Episode 3 — NodeJS MongoDB Backend Architecture / 3.13 — Production Project Structure
3.13.f --- Testing APIs with Postman
In one sentence: Postman turns ad-hoc API testing into a repeatable, documented, and automated workflow --- from manually sending requests to running full regression suites in CI/CD with Newman.
Navigation: <- 3.13.e ESLint & Prettier | 3.13 Exercise Questions ->
1. Setting up Postman collections
A collection is a folder of related API requests. Think of it as a test suite for your API.
Creating a collection for a blog API
Blog API Collection
├── Auth
│ ├── POST Register
│ ├── POST Login
│ ├── POST Logout
│ └── POST Refresh Token
├── Users
│ ├── GET My Profile
│ ├── PATCH Update Profile
│ └── DELETE My Account
├── Posts
│ ├── POST Create Post
│ ├── GET All Posts
│ ├── GET Single Post
│ ├── PATCH Update Post
│ └── DELETE Delete Post
└── Comments
├── POST Add Comment
├── GET Post Comments
└── DELETE Delete Comment
Steps to create
- Click New -> Collection
- Name it:
Blog API - Create folders for each resource (Auth, Users, Posts, Comments)
- Add requests to each folder
- For each request, set: method, URL, headers, body, and optional tests
Request anatomy in Postman
| Field | Example | Purpose |
|---|---|---|
| Method | POST | HTTP verb |
| URL | {{baseUrl}}/api/v1/auth/login | Endpoint with variable |
| Headers | Authorization: Bearer {{token}} | Auth and content type |
| Body | { "email": "...", "password": "..." } | Request payload (JSON) |
| Tests | JavaScript assertions | Validate the response |
| Pre-request Script | JavaScript setup | Run before the request |
2. Environment variables in Postman
Environments let you switch between local, staging, and production without editing every request.
Creating environments
| Variable | Local | Staging | Production |
|---|---|---|---|
baseUrl | http://localhost:3000 | https://staging-api.myapp.com | https://api.myapp.com |
token | (set by login test) | (set by login test) | (set by login test) |
userId | (set dynamically) | (set dynamically) | (set dynamically) |
postId | (set dynamically) | (set dynamically) | (set dynamically) |
Using variables in requests
URL: {{baseUrl}}/api/v1/users/{{userId}}
Header: Authorization: Bearer {{token}}
Body: { "name": "{{userName}}" }
Variables use double curly braces: {{variableName}}.
Variable scopes (priority order)
| Scope | Lifetime | Use case |
|---|---|---|
| Global | Persists across all collections | API keys, utility values |
| Collection | Scoped to one collection | Collection-specific defaults |
| Environment | Switched between envs | baseUrl, token, server-specific values |
| Data | One collection run (from CSV/JSON) | Batch testing with test data |
| Local | One request execution | Temporary values |
If the same variable exists in multiple scopes, narrower scope wins (Local > Data > Environment > Collection > Global).
3. Authentication flow: login, save token, reuse
The most common Postman workflow: login once, use the token for all subsequent requests.
Step 1: Login request
POST {{baseUrl}}/api/v1/auth/login
Body (JSON):
{
"email": "john@example.com",
"password": "Password123!"
}
Step 2: Auto-save token in Tests tab
// Tests tab of the Login request
const response = pm.response.json();
if (response.success && response.data.accessToken) {
pm.environment.set('token', response.data.accessToken);
pm.environment.set('refreshToken', response.data.refreshToken);
pm.environment.set('userId', response.data.user.id);
console.log('Token saved to environment');
}
// Assertions
pm.test('Status is 200', () => {
pm.response.to.have.status(200);
});
pm.test('Response has accessToken', () => {
pm.expect(response.data).to.have.property('accessToken');
});
pm.test('Response has user object', () => {
pm.expect(response.data).to.have.property('user');
pm.expect(response.data.user).to.have.property('id');
pm.expect(response.data.user).to.have.property('email');
});
Step 3: Use token in subsequent requests
Set this in the collection's Authorization tab (inherited by all requests):
Type: Bearer Token
Token: {{token}}
Or set it per-request in the Headers tab:
Authorization: Bearer {{token}}
Step 4: Auto-refresh expired tokens
In the collection's Pre-request Script:
// Pre-request Script (Collection level)
const tokenExpiry = pm.environment.get('tokenExpiry');
const now = Date.now();
if (tokenExpiry && now > tokenExpiry) {
console.log('Token expired, refreshing...');
pm.sendRequest({
url: pm.environment.get('baseUrl') + '/api/v1/auth/refresh',
method: 'POST',
header: { 'Content-Type': 'application/json' },
body: {
mode: 'raw',
raw: JSON.stringify({
refreshToken: pm.environment.get('refreshToken'),
}),
},
}, (err, res) => {
if (!err && res.code === 200) {
const data = res.json().data;
pm.environment.set('token', data.accessToken);
pm.environment.set('tokenExpiry', Date.now() + 3600000); // 1 hour
}
});
}
4. Automated testing in Postman
Postman's Tests tab runs JavaScript after every request. Use it for assertions.
Common test patterns
// --- Status code checks ---
pm.test('Status is 200 OK', () => {
pm.response.to.have.status(200);
});
pm.test('Status is 201 Created', () => {
pm.response.to.have.status(201);
});
// --- Response structure ---
pm.test('Response has correct structure', () => {
const res = pm.response.json();
pm.expect(res).to.have.property('success', true);
pm.expect(res).to.have.property('statusCode', 200);
pm.expect(res).to.have.property('data');
pm.expect(res).to.have.property('message');
});
// --- Data validation ---
pm.test('User has required fields', () => {
const user = pm.response.json().data;
pm.expect(user).to.have.property('id');
pm.expect(user).to.have.property('name');
pm.expect(user).to.have.property('email');
pm.expect(user).to.not.have.property('password'); // Password should never be returned
});
// --- Array responses ---
pm.test('Posts array is not empty', () => {
const posts = pm.response.json().data.posts;
pm.expect(posts).to.be.an('array');
pm.expect(posts.length).to.be.greaterThan(0);
});
// --- Response time ---
pm.test('Response time is under 500ms', () => {
pm.expect(pm.response.responseTime).to.be.below(500);
});
// --- Headers ---
pm.test('Content-Type is application/json', () => {
pm.response.to.have.header('Content-Type', /application\/json/);
});
// --- Error responses ---
pm.test('Returns 401 for invalid token', () => {
pm.response.to.have.status(401);
const res = pm.response.json();
pm.expect(res.success).to.be.false;
pm.expect(res.message).to.include('token');
});
// --- Save values for next requests ---
pm.test('Save post ID for later', () => {
const post = pm.response.json().data;
pm.environment.set('postId', post.id);
});
Test result output
PASS Status is 200 OK
PASS Response has correct structure
PASS User has required fields
FAIL Response time is under 500ms (actual: 723ms)
5. Collection runner for regression testing
The Collection Runner executes every request in a collection sequentially --- simulating a full user workflow.
Setting up a test workflow
1. POST Register --> creates user, saves token
2. POST Login --> logs in, saves new token
3. GET My Profile --> verifies auth works
4. POST Create Post --> creates a post, saves postId
5. GET All Posts --> verifies post appears in list
6. GET Single Post --> verifies post details
7. PATCH Update Post --> updates the post
8. POST Add Comment --> adds comment to the post
9. GET Post Comments --> verifies comment appears
10. DELETE Delete Post --> cleans up
11. DELETE My Account --> cleans up
Running the collection
- Click the Run button on the collection
- Select the environment (Local, Staging, etc.)
- Set iteration count (how many times to run)
- Optionally upload a data file (CSV/JSON) for parameterised testing
- Click Run Collection
Data-driven testing with CSV
email,password,name
user1@test.com,Pass123!,User One
user2@test.com,Pass456!,User Two
user3@test.com,Pass789!,User Three
Reference in request body:
{
"email": "{{email}}",
"password": "{{password}}",
"name": "{{name}}"
}
The runner iterates once per row, substituting variables from the CSV.
6. Newman: CLI runner for CI/CD
Newman runs Postman collections from the command line --- perfect for CI/CD pipelines.
Installation
npm install -g newman
Basic usage
# Export collection and environment from Postman as JSON files
newman run Blog-API.postman_collection.json \
--environment Local.postman_environment.json
Newman options
| Flag | Purpose | Example |
|---|---|---|
--environment | Environment file | Local.postman_environment.json |
--globals | Global variables file | globals.json |
--iteration-count | Run N times | --iteration-count 5 |
--iteration-data | CSV/JSON data file | --iteration-data users.csv |
--reporters | Output format | --reporters cli,json,html |
--reporter-html-export | HTML report path | --reporter-html-export report.html |
--delay-request | Delay between requests (ms) | --delay-request 500 |
--timeout-request | Per-request timeout | --timeout-request 10000 |
--bail | Stop on first failure | --bail |
CI/CD integration (GitHub Actions)
# .github/workflows/api-tests.yml
name: API Tests
on: [push, pull_request]
jobs:
api-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Start server
run: npm start &
env:
NODE_ENV: test
PORT: 3000
MONGODB_URI: ${{ secrets.TEST_MONGODB_URI }}
- name: Wait for server
run: npx wait-on http://localhost:3000/health
- name: Install Newman
run: npm install -g newman newman-reporter-htmlextra
- name: Run API tests
run: |
newman run ./postman/Blog-API.postman_collection.json \
--environment ./postman/Test.postman_environment.json \
--reporters cli,htmlextra \
--reporter-htmlextra-export ./newman-report.html
- name: Upload test report
if: always()
uses: actions/upload-artifact@v4
with:
name: newman-report
path: ./newman-report.html
7. Documenting APIs with Postman
Postman doubles as an API documentation tool.
Adding documentation to requests
For each request, add:
- Description in the request body (Markdown supported)
- Example responses (save successful and error responses)
- Parameter descriptions in the Params/Headers/Body tabs
Example request documentation
## Create Post
Creates a new blog post. Requires authentication.
### Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| title | string | Yes | Post title (3-200 characters) |
| content | string | Yes | Post body (Markdown supported) |
| tags | string[] | No | Array of tag strings |
| status | string | No | "draft" or "published" (default: "draft") |
### Success Response (201)
{
"success": true,
"statusCode": 201,
"data": { ... },
"message": "Post created successfully"
}
### Error Response (400)
{
"success": false,
"statusCode": 400,
"message": "Validation failed: title is required"
}
Publishing documentation
- Click on the collection -> View Documentation
- Click Publish to generate a public URL
- Share the URL with frontend developers, partners, or clients
- Documentation auto-updates when you modify the collection
8. Mock servers in Postman
Mock servers return predefined responses without a running backend --- useful for frontend teams working in parallel.
Creating a mock server
- Select a collection
- Click Mock Collection
- Postman generates a mock URL:
https://abc123.mock.pstmn.io - Add example responses to each request (these become the mock data)
- Frontend developers use the mock URL as their
baseUrl
How mocks match requests
| Request | Mock matches on |
|---|---|
GET /api/v1/posts | Method + path |
POST /api/v1/auth/login | Method + path + example name |
When to use mock servers
| Scenario | Benefit |
|---|---|
| Frontend development before backend is ready | Frontend team is not blocked |
| Testing error scenarios | Return 500, 401, 404 without breaking the real server |
| Demo to stakeholders | Show API behaviour without deploying |
| Contract testing | Both teams agree on the response shape upfront |
9. Team collaboration with Postman workspaces
Workspace types
| Type | Visibility | Use case |
|---|---|---|
| Personal | Only you | Experiments, personal collections |
| Team | All team members | Shared API collections, environments |
| Public | Anyone on the internet | Open-source API documentation |
Collaboration workflow
1. Create a Team Workspace: "Blog API Team"
2. Move the collection into the workspace
3. Invite team members
4. Everyone sees the same collection, environments, and mock servers
5. Changes sync in real-time
6. Use version history to track changes and revert if needed
Best practices for team collections
| Practice | Why |
|---|---|
| Use environment variables for all URLs | Switching between local/staging/prod is one click |
| Never hardcode tokens in requests | Use the auth flow to set them dynamically |
| Add descriptions to every request | New team members understand the API without reading code |
| Save example responses | Documents expected behaviour and powers mock servers |
| Use folders that match your API structure | Navigation mirrors the codebase |
| Name requests clearly | POST Create User not POST /api/v1/users |
10. Postman alternatives
| Tool | Type | Strength |
|---|---|---|
| Postman | GUI + CLI (Newman) | Full-featured, team collaboration, documentation |
| Insomnia | GUI | Cleaner UI, open-source core, GraphQL support |
| Thunder Client | VS Code extension | Lightweight, stays in your editor |
| HTTPie | CLI | Human-friendly command-line HTTP client |
| curl | CLI | Universal, available everywhere, scriptable |
| REST Client | VS Code extension | Write HTTP requests in .http files |
11. Key takeaways
- Collections organise requests by resource; environments switch between local/staging/production.
- Auto-save tokens in login test scripts so subsequent requests authenticate automatically.
- Tests tab runs assertions after every request --- validate status codes, response structure, and data.
- Collection Runner executes workflows sequentially; data files enable parameterised testing.
- Newman brings Postman into CI/CD --- run collections in GitHub Actions, Jenkins, or any pipeline.
- Mock servers unblock frontend development by returning predefined responses.
- Team workspaces keep collections, environments, and documentation in sync across the team.
Explain-It Challenge
Explain without notes:
- You join a new team and they hand you a Postman collection. Describe the steps to get it running against your local development server.
- Why should you save the auth token in Postman's environment (via test scripts) instead of hardcoding it in the Authorization header?
- A CI/CD pipeline needs to run API tests after every deployment. Describe the Newman setup: what files are needed, what commands run, and how failures are reported.
Navigation: <- 3.13.e ESLint & Prettier | 3.13 Exercise Questions ->