You can mock API Gateway responses by returning a specific HTTP status code and a predefined JSON body from your Lambda function.

Here’s a quick demo:

// Example Lambda function (Node.js)
exports.handler = async (event) => {
    const response = {
        statusCode: 200,
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify({
            message: "This is a mocked response!",
            timestamp: new Date().toISOString()
        })
    };
    return response;
};

When API Gateway receives this response from Lambda, it forwards it directly to the client. This is incredibly useful for testing front-end applications or other services that depend on your API, without needing to implement the full backend logic for every scenario. You can simulate successful responses, errors, or even specific data payloads that might be hard to generate with real backend processing.

Let’s break down how this works and what you can control.

The core idea is that API Gateway acts as a proxy. It sends a request to your integration (in this case, a Lambda function) and then takes the response from that integration and sends it back to the client. The key is that the response format from the integration must match what API Gateway expects. For Lambda integrations, this is a specific JSON structure.

The statusCode in the Lambda response directly maps to the HTTP status code returned to the client. So, statusCode: 200 becomes a 200 OK, statusCode: 404 becomes a 404 Not Found, and so on.

The headers object allows you to set response headers. Content-Type: application/json is crucial if your body is JSON, telling the client how to interpret the data. You can add other headers like Access-Control-Allow-Origin for CORS, or custom headers for specific application logic.

The body is the payload of your response. For JSON, you must stringify it using JSON.stringify(). API Gateway will then send this string as the response body.

To make this more concrete, imagine you’re testing a user profile page. You might want to mock a successful user fetch, a user not found scenario, or a scenario where the user’s data is incomplete.

Scenario 1: Successful User Fetch

// Lambda response for successful fetch
{
    "statusCode": 200,
    "headers": {
        "Content-Type": "application/json"
    },
    "body": "{\"userId\": \"user123\", \"username\": \"testuser\", \"email\": \"test@example.com\"}"
}

Scenario 2: User Not Found

// Lambda response for user not found
{
    "statusCode": 404,
    "headers": {
        "Content-Type": "application/json"
    },
    "body": "{\"error\": \"User not found\"}"
}

Scenario 3: Internal Server Error

// Lambda response for server error
{
    "statusCode": 500,
    "headers": {
        "Content-Type": "application/json"
    },
    "body": "{\"error\": \"An internal error occurred\"}"
}

You can even use query string parameters or path parameters from the incoming API Gateway request (event.queryStringParameters, event.pathParameters) within your Lambda function to dynamically decide which mock response to return. This allows for more sophisticated mocking without deploying multiple API versions.

For example, if you want to mock a specific user ID, your Lambda could look like this:

exports.handler = async (event) => {
    const userId = event.pathParameters.userId; // Assuming /users/{userId} path

    if (userId === 'mock-not-found') {
        return {
            statusCode: 404,
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ error: "User not found for ID: " + userId })
        };
    } else {
        return {
            statusCode: 200,
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ userId: userId, username: "mockuser_" + userId, email: userId + "@mock.com" })
        };
    }
};

The most surprising thing about this pattern is how little you need to do on the API Gateway side. Once your Lambda function is configured as the integration for a specific API Gateway resource and method, API Gateway is already set up to expect this JSON response format. You don’t need to define response templates in API Gateway for Lambda proxy integrations. This simplicity is a key benefit.

However, what many don’t realize is the power of isBase64Encoded. If your Lambda function needs to return binary data (like an image or a PDF), you can set isBase64Encoded: true and then provide the base64-encoded string in the body. API Gateway will correctly decode this and send the binary data to the client.

// Lambda response for binary data (e.g., an image)
{
    "statusCode": 200,
    "headers": {
        "Content-Type": "image/png"
    },
    "body": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=", // Base64 encoded 1x1 transparent PNG
    "isBase64Encoded": true
}

This makes API Gateway a capable intermediary for serving static assets or any binary content directly from Lambda, further simplifying your architecture.

The next concept to explore is how to handle different HTTP methods (GET, POST, PUT, DELETE) for the same API endpoint using a single Lambda function, and how to differentiate them within the event object.

Want structured learning?

Take the full Apigateway course →