Serverless development has become one of the most effective ways to build small, event-driven applications without managing infrastructure. When combined with TypeScript, AWS SAM (Serverless Application Model), and Express, it becomes possible to create a strongly typed yet flexible API backed by AWS Lambda and API Gateway. This post walks through setting up a minimal Lambda function that responds to a single /ping request with “pong”. It will also include basic unit and integration tests to demonstrate local development and testability.
We’ll use the AWS SAM (Serverless Application Model) CLI to scaffold and deploy the project. To begin, create a new project using the TypeScript template provided by SAM.
sam init --runtime nodejs18.x --name lambda-ping --app-template hello-world-typescript
Once the project has been generated, change into the new directory:
cd lambda-ping
The SAM template creates a folder named src/hello-world that contains the Lambda handler. Rename this folder to ping to reflect its purpose.
# Windows:
rename src\hello-world ping
# macOS or Linux:
mv src/hello-world src/ping
Then update the path references in both template.yaml and tests/unit accordingly.
The Lambda handler is defined in src/ping/app.ts. Replace the default contents of that file with the following code, which directly implements the AWS API Gateway proxy response format:
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
export const handler = async (
event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> => {
if (event.httpMethod === 'GET' && event.path === '/ping') {
return {
statusCode: 200,
headers: { 'Content-Type': 'text/plain' },
body: 'pong',
};
}
return {
statusCode: 404,
headers: { 'Content-Type': 'text/plain' },
body: 'Not Found',
};
};
This handler checks for a GET request to /ping and returns a plain-text “pong” response. All other requests receive a 404. The shape of the function strictly follows the AWS Lambda proxy integration model, with strongly typed input and output using TypeScript interfaces from the aws-lambda package, which comes bundled with the SAM template.
Next, open template.yaml and update the configuration so that API Gateway routes requests to /ping using the GET method:
Events:
Ping:
Type: Api
Properties:
Path: /ping
Method: get
With this setup complete, you can run the Lambda locally using the SAM local API emulator. This command starts a local API Gateway simulation that routes requests to the handler:
sam local start-api
Once the server is running, sending a GET request to http://localhost:3000/ping will return the expected “pong” response. You can test this using a browser, curl, or Postman.
To validate functionality at the unit level, write a test that directly invokes the handler function. Start by creating a new file ping.unit.test.ts in the tests/unit directory. Inside, simulate the event that API Gateway would normally pass to Lambda:
import { handler } from '../../../src/ping/app';
import { APIGatewayProxyEvent } from 'aws-lambda';
const baseEvent: Partial<APIGatewayProxyEvent> = {
httpMethod: 'GET',
path: '/ping',
};
describe('Lambda handler', () => {
it('returns pong for GET /ping', async () => {
const response = await handler(baseEvent as any);
expect(response.statusCode).toBe(200);
expect(response.body).toBe('pong');
});
it('returns 404 for unknown paths', async () => {
const response = await handler({
...baseEvent,
path: '/unknown',
} as any);
expect(response.statusCode).toBe(404);
expect(response.body).toBe('Not Found');
});
});
The unit test verifies two cases: a successful response to /ping and a fallback response to unknown paths. These tests don’t require the SAM CLI and can be run directly using Jest.
To execute the tests:
npx jest
You can also test the deployed handler in a more integrated way using SAM’s local event simulation. Start by creating a JSON file at the root of the project called event.json:
{
"httpMethod": "GET",
"path": "/ping"
}
Then invoke the Lambda manually:
sam local invoke PingFunction --event event.json
This simulates an API Gateway-triggered Lambda invocation. The output will include the full response object, including headers and body.
Once all tests are passing, you can deploy the application to your AWS account:
sam deploy --guided
This command walks through the deployment process, allowing you to choose a stack name, region, and other parameters. After deployment, SAM will provide the public endpoint for the /ping route. Visiting this URL will confirm that the Lambda is functioning as expected.
This setup requires no frameworks, no Express, and no unnecessary abstraction. By relying solely on AWS-native tools and the standard Lambda interface, the result is a clean, fast, minimal API that is easy to test, easy to reason about, and deployable in just a few minutes. While frameworks can offer shortcuts and comfort, sometimes the most effective path is also the simplest — and in this case, that path runs straight through AWS itself.
Suleyman Cabir Ataman, PhD