Express API Testing with Postman - A Comprehensive Guide

Testing authentication flows and API endpoints in Express applications

Introduction to API Testing

When building a web application with a separate frontend and backend, testing your API endpoints becomes crucial to ensure your application functions correctly. Think of API testing like checking the plumbing in a house before the walls go up - it's much easier to fix issues when you can directly interact with the pipes!

In this guide, we'll explore how to use Postman, a popular API testing tool, to validate the endpoints in our Express application. Postman is like a Swiss Army knife for API developers - it allows you to send requests, examine responses, automate tests, and document your API all in one place.

What You'll Learn

  • Setting up Postman for testing Express APIs
  • Testing authentication flows (signup, login, logout)
  • Working with cookies and CSRF protection
  • Validating request inputs and handling errors
  • Creating a collection of tests for your API

Setting Up Postman

Before diving into testing our Express API endpoints, we need to set up Postman properly. Think of this as preparing your workbench before starting a project.

Installing Postman

If you haven't already, download and install Postman from the official website. Postman is available for Windows, macOS, and Linux.

Creating a Collection

Collections in Postman are like folders that group related requests together. For our Express API testing, we'll create a collection named "Express Authentication API".

  1. Open Postman and click the "New" button
  2. Select "Collection"
  3. Name your collection "Express Authentication API"
  4. Click "Create"

Setting Up Environment Variables

Environment variables in Postman are like storage containers for values you'll use repeatedly. They're especially useful for things like base URLs and tokens. Think of them as recipe ingredients you keep within arm's reach when cooking.

  1. Click the gear icon in the top right and select "Add"
  2. Name your environment "Express API Local"
  3. Add a variable named "baseUrl" with the value "http://localhost:8000"
  4. Add a variable named "xsrfToken" (leave it empty for now)
  5. Click "Save"
  6. Select your new environment from the dropdown in the top right

Environment Variables

  • baseUrl: http://localhost:8000
  • xsrfToken: (to be filled later)

Managing CSRF Protection

Cross-Site Request Forgery (CSRF) protection is a security feature that prevents unauthorized commands from being executed by a user who's already authenticated. Think of it as the safety lock on a gun - it prevents accidental or malicious firing.

Our Express application uses a CSRF token system that requires:

  1. Getting a CSRF token from the server
  2. Sending that token back with any POST, PUT, PATCH, or DELETE requests

Getting a CSRF Token

First, let's create a request to get a CSRF token that we'll use for subsequent requests:

  1. In your collection, click "Add request"
  2. Name it "Get CSRF Token"
  3. Set the method to GET
  4. Set the URL to {{baseUrl}}/api/csrf/restore
  5. Go to the "Tests" tab and add this script to automatically save the token:
// Get the XSRF-TOKEN cookie
const xsrfCookie = pm.cookies.get('XSRF-TOKEN', 'http://localhost:8000');

// Store it as an environment variable if it exists
if (xsrfCookie) {
    pm.environment.set('xsrfToken', xsrfCookie);
    console.log("XSRF token saved: " + xsrfCookie);
} else {
    console.log("No XSRF token found in cookies!");
}
                
or
// Get the CSRF token from body
                    const responseJson = pm.response.json();
                    if (responseJson && responseJson["XSRF-Token"]) {
                        pm.environment.set("csrfToken", responseJson["XSRF-Token"]);
                        console.log("CSRF Token captured from response body:", responseJson["XSRF-Token"]);
                    }

Save this request and run it. If successful, your "xsrfToken" environment variable should now be populated with the CSRF token.

Why This Matters

The CSRF token is like a temporary password that changes with each session. Without it, the server will reject any requests that attempt to modify data (POST, PUT, DELETE). This script automatically extracts the token from cookies and makes it available for all your other requests.

Testing the API Router

Before testing our authentication endpoints, let's verify that our basic API router is set up correctly using the test endpoint. This is like checking if your doorbell works before testing the entire home security system.

Test POST Request to /api/test

  1. In your collection, click "Add request"
  2. Name it "Test API Router"
  3. Set the method to POST
  4. Set the URL to {{baseUrl}}/api/test
  5. Go to the "Headers" tab and add:

Headers

  • Content-Type: application/json
  • XSRF-TOKEN: {{xsrfToken}}

Next, add a simple JSON body:

{
    "hello": "world"
}
                

Save and send this request. You should receive a response like:

{
    "requestBody": {
        "hello": "world"
    }
}
                

What's Happening Here?

This test confirms that:

  • Your Express server is running
  • The API router is properly connected
  • CSRF protection is working
  • JSON parsing middleware is functioning

Think of this as turning on the car's ignition before driving - it's a basic check that the system is operational.

Testing Error Handling

Error handling is a critical part of any robust API. Like guardrails on a highway, proper error responses prevent catastrophic failures and guide users back to the correct path.

Let's test our application's error handling by intentionally requesting a non-existent resource.

Testing Resource Not Found Error

  1. Add a new request to your collection
  2. Name it "Test 404 Error"
  3. Set the method to GET
  4. Set the URL to {{baseUrl}}/api/nonexistent

Send this request. You should receive a 404 status code and a response like:

{
    "title": "Resource Not Found",
    "message": "The requested resource couldn't be found.",
    "errors": {
        "message": "The requested resource couldn't be found."
    },
    "stack": "Error: The requested resource couldn't be found.\n ..."
}
                

This confirms that our error-handling middleware is working properly. In a production environment, the stack trace would be omitted.

Testing Authentication Endpoints

Authentication is the foundation of user security in web applications. Think of it as the bouncer at the club door - it ensures only authorized users can access protected resources.

Our Express application implements a token-based authentication system using JWT (JSON Web Tokens) stored in HTTP-only cookies.

Testing User Signup

First, let's test the signup endpoint which creates a new user.

  1. Add a new request to your collection
  2. Name it "User Signup"
  3. Set the method to POST
  4. Set the URL to {{baseUrl}}/api/users
  5. Add the headers:

Headers

  • Content-Type: application/json
  • XSRF-TOKEN: {{xsrfToken}}

Add the following JSON body:

{
    "email": "test@example.com",
    "username": "testuser",
    "password": "password123",
    "firstName": "Test",
    "lastName": "User"
}
                

Send this request. You should receive a 200 status and a response containing the user information (without the password). The response should look something like:

{
    "user": {
        "id": 4,
        "email": "test@example.com",
        "username": "testuser",
        "firstName": "Test",
        "lastName": "User"
    }
}
                

Additionally, check the "Cookies" tab in Postman. You should see a "token" cookie has been set. This is the JWT that will be used for authentication in subsequent requests.

Testing Signup Validation

Now let's test the input validation for the signup endpoint. Think of input validation as a bouncer checking IDs - it's the first line of defense against bad data.

Create a new request called "User Signup Validation Test" similar to the previous one, but with invalid data:

{
    "email": "not-an-email",
    "username": "a",
    "password": "short"
}
                

Send this request. You should receive a 400 Bad Request status and a response with validation errors:

{
    "title": "Bad request.",
    "message": "Bad request.",
    "errors": {
        "email": "Please provide a valid email.",
        "username": "Please provide a username with at least 4 characters.",
        "password": "Password must be 6 characters or more."
    }
}
                

Input Validation Examples

Try testing these validation cases:

  • Empty email: "email": ""
  • Username too short: "username": "abc"
  • Username as email: "username": "user@example.com"
  • Password too short: "password": "12345"

Testing User Login

Next, let's test the login endpoint. First, make sure you've signed out (we'll create that request later) or are using a new Postman session.

  1. Add a new request to your collection
  2. Name it "User Login"
  3. Set the method to POST
  4. Set the URL to {{baseUrl}}/api/session
  5. Add the headers:

Headers

  • Content-Type: application/json
  • XSRF-TOKEN: {{xsrfToken}}

Add the following JSON body to login with username:

{
    "credential": "testuser",
    "password": "password123"
}
                

Send this request. You should receive a 200 status and a response containing the user information:

{
    "user": {
        "id": 4,
        "email": "test@example.com",
        "username": "testuser",
        "firstName": "Test",
        "lastName": "User"
    }
}
                

Now let's try logging in with email instead of username. Create a new request or modify the previous one:

{
    "credential": "test@example.com",
    "password": "password123"
}
                

This should also return a 200 status with the same user information.

Testing Login Validation

Let's test the validation for the login endpoint:

{
    "credential": "",
    "password": "password123"
}
                

This should return a 400 status with an error about providing a valid credential.

Now test with an incorrect password:

{
    "credential": "testuser",
    "password": "wrongpassword"
}
                

This should return a 401 Unauthorized status with an error about invalid credentials.

Testing Get Current User Session

Let's test retrieving the current user's session information:

  1. Add a new request to your collection
  2. Name it "Get Current User"
  3. Set the method to GET
  4. Set the URL to {{baseUrl}}/api/session

If you're logged in (have a valid token cookie), this should return the user's information:

{
    "user": {
        "id": 4,
        "email": "test@example.com",
        "username": "testuser",
        "firstName": "Test",
        "lastName": "User"
    }
}
                

If you're not logged in, it should return:

{
    "user": null
}
                

Testing User Logout

Finally, let's test the logout endpoint:

  1. Add a new request to your collection
  2. Name it "User Logout"
  3. Set the method to DELETE
  4. Set the URL to {{baseUrl}}/api/session
  5. Add the headers:

Headers

  • Content-Type: application/json
  • XSRF-TOKEN: {{xsrfToken}}

Send this request. You should receive a 200 status and a response like:

{
    "message": "success"
}
                

The "token" cookie should be cleared. You can verify this by running the "Get Current User" request again, which should now return { "user": null }.

Creating a Testing Workflow

Now that we've tested individual endpoints, let's organize them into a logical workflow. Think of this as creating a test script for a play - each action follows the next in a natural sequence.

Sample Testing Workflow

Here's a recommended sequence for testing your API:

  1. Get CSRF Token
  2. Test API Router (POST /api/test)
  3. Test 404 Error
  4. User Signup
  5. User Logout
  6. User Login
  7. Get Current User (should return user info)
  8. User Logout
  9. Get Current User (should return null)

In Postman, you can create this workflow as a "Collection Run" by:

  1. Click the "Runner" button in the top bar
  2. Select your "Express Authentication API" collection
  3. Arrange your requests in the desired order
  4. Click "Run"

This will execute all requests in sequence, allowing you to see if your entire authentication flow works correctly.

Advanced Postman Techniques

As you become more comfortable with Postman, consider these advanced techniques to improve your API testing experience:

Automated Tests

You can add automated tests to each request using the "Tests" tab. For example, for the login request:

// Check if request was successful
pm.test("Status code is 200", function() {
    pm.response.to.have.status(200);
});

// Check if response has the correct structure
pm.test("Response has user object", function() {
    const responseJson = pm.response.json();
    pm.expect(responseJson).to.have.property('user');
    pm.expect(responseJson.user).to.have.property('id');
    pm.expect(responseJson.user).to.have.property('username');
});

// Check if token cookie was set
pm.test("Token cookie is present", function() {
    pm.expect(pm.cookies.has('token')).to.be.true;
});
                

Using Variables for Test Data

Create environment variables for test usernames, emails, and passwords to easily switch between test cases:

Environment Variables for Test Data

  • testUsername: testuser
  • testEmail: test@example.com
  • testPassword: password123

Then use these in your request bodies:

{
    "credential": "{{testUsername}}",
    "password": "{{testPassword}}"
}
                

Creating Reusable Scripts

For operations you perform frequently, like getting a CSRF token, you can create a reusable script in the "Pre-request Script" tab of your collection:

// Pre-request script to get CSRF token if needed
if (!pm.environment.get('xsrfToken')) {
    const getCsrfRequest = {
        url: pm.environment.get('baseUrl') + '/api/csrf/restore',
        method: 'GET'
    };
    
    pm.sendRequest(getCsrfRequest, function (err, res) {
        if (err) {
            console.error(err);
        } else {
            const xsrfCookie = res.cookies.find(cookie => cookie.name === 'XSRF-TOKEN');
            if (xsrfCookie) {
                pm.environment.set('xsrfToken', xsrfCookie.value);
                console.log("XSRF token refreshed: " + xsrfCookie.value);
            }
        }
    });
}
                

Real-World Application

The authentication system you've built and tested forms the backbone of many web applications. Here are some real-world examples of how these patterns are used:

E-commerce Platforms

In an e-commerce application, the authentication system is used to:

Social Media Applications

Social platforms leverage authentication to:

SaaS Products

Software-as-a-Service applications use authentication to:

By testing these authentication flows thoroughly, you're ensuring the foundation of your application is solid and secure.

Wrapping Up

Testing your API endpoints with Postman is like rehearsing a play before opening night - it helps identify issues before your users do and ensures everything runs smoothly when it matters most.

In this guide, we've covered:

Remember that thorough API testing is an investment that pays dividends in reduced bugs, improved user experience, and saved development time. As your application grows, consider expanding your tests to cover new endpoints and edge cases.

Next Steps

To further enhance your API testing skills, consider exploring:

  • Automated testing with Postman test scripts
  • Integrating Postman tests into CI/CD pipelines
  • Performance testing to ensure your API can handle load
  • Security testing to identify vulnerabilities

Project File Structure Reference

For reference, here's a map of the important files in our Express application:

backend/
├── app.js                 # Main application setup
├── routes/
│   ├── index.js           # Main router
│   └── api/
│       ├── index.js       # API router with restoreUser middleware
│       ├── session.js     # Login, logout, and session endpoints
│       └── users.js       # User signup endpoint
├── db/
│   ├── models/
│   │   └── user.js        # User model with validations
│   ├── migrations/
│   │   └── [timestamp]-create-user.js  # User table migration
│   └── seeders/
│       └── [timestamp]-demo-user.js    # Seed data for users
└── utils/
    ├── auth.js            # Authentication helper functions
    └── validation.js      # Request validation middleware