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

  1. Click New -> Collection
  2. Name it: Blog API
  3. Create folders for each resource (Auth, Users, Posts, Comments)
  4. Add requests to each folder
  5. For each request, set: method, URL, headers, body, and optional tests

Request anatomy in Postman

FieldExamplePurpose
MethodPOSTHTTP verb
URL{{baseUrl}}/api/v1/auth/loginEndpoint with variable
HeadersAuthorization: Bearer {{token}}Auth and content type
Body{ "email": "...", "password": "..." }Request payload (JSON)
TestsJavaScript assertionsValidate the response
Pre-request ScriptJavaScript setupRun before the request

2. Environment variables in Postman

Environments let you switch between local, staging, and production without editing every request.

Creating environments

VariableLocalStagingProduction
baseUrlhttp://localhost:3000https://staging-api.myapp.comhttps://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)

ScopeLifetimeUse case
GlobalPersists across all collectionsAPI keys, utility values
CollectionScoped to one collectionCollection-specific defaults
EnvironmentSwitched between envsbaseUrl, token, server-specific values
DataOne collection run (from CSV/JSON)Batch testing with test data
LocalOne request executionTemporary 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

  1. Click the Run button on the collection
  2. Select the environment (Local, Staging, etc.)
  3. Set iteration count (how many times to run)
  4. Optionally upload a data file (CSV/JSON) for parameterised testing
  5. 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

FlagPurposeExample
--environmentEnvironment fileLocal.postman_environment.json
--globalsGlobal variables fileglobals.json
--iteration-countRun N times--iteration-count 5
--iteration-dataCSV/JSON data file--iteration-data users.csv
--reportersOutput format--reporters cli,json,html
--reporter-html-exportHTML report path--reporter-html-export report.html
--delay-requestDelay between requests (ms)--delay-request 500
--timeout-requestPer-request timeout--timeout-request 10000
--bailStop 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

  1. Click on the collection -> View Documentation
  2. Click Publish to generate a public URL
  3. Share the URL with frontend developers, partners, or clients
  4. 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

  1. Select a collection
  2. Click Mock Collection
  3. Postman generates a mock URL: https://abc123.mock.pstmn.io
  4. Add example responses to each request (these become the mock data)
  5. Frontend developers use the mock URL as their baseUrl

How mocks match requests

RequestMock matches on
GET /api/v1/postsMethod + path
POST /api/v1/auth/loginMethod + path + example name

When to use mock servers

ScenarioBenefit
Frontend development before backend is readyFrontend team is not blocked
Testing error scenariosReturn 500, 401, 404 without breaking the real server
Demo to stakeholdersShow API behaviour without deploying
Contract testingBoth teams agree on the response shape upfront

9. Team collaboration with Postman workspaces

Workspace types

TypeVisibilityUse case
PersonalOnly youExperiments, personal collections
TeamAll team membersShared API collections, environments
PublicAnyone on the internetOpen-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

PracticeWhy
Use environment variables for all URLsSwitching between local/staging/prod is one click
Never hardcode tokens in requestsUse the auth flow to set them dynamically
Add descriptions to every requestNew team members understand the API without reading code
Save example responsesDocuments expected behaviour and powers mock servers
Use folders that match your API structureNavigation mirrors the codebase
Name requests clearlyPOST Create User not POST /api/v1/users

10. Postman alternatives

ToolTypeStrength
PostmanGUI + CLI (Newman)Full-featured, team collaboration, documentation
InsomniaGUICleaner UI, open-source core, GraphQL support
Thunder ClientVS Code extensionLightweight, stays in your editor
HTTPieCLIHuman-friendly command-line HTTP client
curlCLIUniversal, available everywhere, scriptable
REST ClientVS Code extensionWrite HTTP requests in .http files

11. Key takeaways

  1. Collections organise requests by resource; environments switch between local/staging/production.
  2. Auto-save tokens in login test scripts so subsequent requests authenticate automatically.
  3. Tests tab runs assertions after every request --- validate status codes, response structure, and data.
  4. Collection Runner executes workflows sequentially; data files enable parameterised testing.
  5. Newman brings Postman into CI/CD --- run collections in GitHub Actions, Jenkins, or any pipeline.
  6. Mock servers unblock frontend development by returning predefined responses.
  7. Team workspaces keep collections, environments, and documentation in sync across the team.

Explain-It Challenge

Explain without notes:

  1. You join a new team and they hand you a Postman collection. Describe the steps to get it running against your local development server.
  2. Why should you save the auth token in Postman's environment (via test scripts) instead of hardcoding it in the Authorization header?
  3. 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 ->