In this article, we will create a serverless application using AWS Lambda, TypeScript, and the Serverless Framework. This application will have a routing structure similar to Next.js, where routes are defined in a routes folder using TypeScript files. It will also support a _layout approach for wrapping content in the same folder and in child folders. The application will be built using serverless-bundle for efficiency.

Prerequisites

Step-by-step guide

  1. Create a new Serverless service:

In your terminal, run the following command to create a new Serverless service using the aws-nodejs-typescript template:

serverless create --template aws-nodejs-typescript --path my-serverless-app

Change into the my-serverless-app directory:

cd my-serverless-app

  1. Install dependencies:

Run the following command to install the required dependencies:

npm install

  1. Update serverless.yml:

Replace the contents of the serverless.yml file with the following configuration:

service: my-serverless-app

frameworkVersion: '2'

provider:
  name: aws
  runtime: nodejs14.x
  lambdaHashingVersion: 20201221
  stage: ${opt:stage, 'dev'}

functions:
  app:
    handler: src/app.handler
    events:
      - http:
          path: /{proxy+}
          method: any
          cors: true

plugins:
  - serverless-bundle
  - serverless-offline

custom:
  bundle:
    sourcemaps: false
    caching: false
    tsconfig: tsconfig.json
    excludeDevDependencies: false

  1. Create src/app.ts:

Create a new file named app.ts in the src directory with the following content:

import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

function capitalizeFirstLetter(str: string): string {
  return str.charAt(0).toUpperCase() + str.slice(1);
}

function removeStagePrefix(path: string, stage: string): string {
  if (path.startsWith(`/${stage}`)) {
    return path.substring(stage.length + 1);
  }
  return path;
}

export const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
  const stage = event.requestContext.stage;
  const originalPath = event.pathParameters?.proxy || '';
  const routePath = removeStagePrefix(originalPath, stage);
  const routeMethod = event.httpMethod.toLowerCase();
  const capitalizedRouteMethod = capitalizeFirstLetter(routeMethod);

  try {
    const routeModule = await import(`./routes/${routePath}`);
    const routeHandler = routeModule[`onRequest${capitalizedRouteMethod}`];

    if (routeHandler) {
      return await routeHandler(event);
    } else {
      throw new Error('No route handler found');
    }
  } catch (error) {
    return {
      statusCode: 404,
      body: 'Not found',
    };
  }
};