Skip to content

Smithy API to Relational Database

The connection generator wires a Smithy API to a Relational Database project, injecting a Prisma client into the service context so all operation implementations can access the database.

Before using this generator, ensure you have:

  1. A ts#smithy-api project (TypeScript backend)
  2. A ts#rdb project
  1. Install the Nx Console VSCode Plugin if you haven't already
  2. Open the Nx Console in VSCode
  3. Click Generate (UI) in the "Common Nx Commands" section
  4. Search for @aws/nx-plugin - connection
  5. Fill in the required parameters
    • Click Generate

    Select your Smithy API backend project as the source and your relational database project as the target.

    Parameter Type Default Description
    sourceProject Required string - The source project
    targetProject Required string - The target project to connect to
    sourceComponent string - The source component to connect from (component name, path relative to source project root, or generator id). Use '.' to explicitly select the project as the source.
    targetComponent string - The target component to connect to (component name, path relative to target project root, or generator id). Use '.' to explicitly select the project as the target.

    The generator modifies three existing files in your Smithy API backend:

    • Directorypackages/api/src
      • context.ts db property added to ServiceContext
      • handler.ts Prisma client created inside lambdaHandler, passed to serviceHandler.handle
      • local-server.ts Prisma client created inside the request handler, passed to serviceHandler.handle

    Additionally, it updates the API’s serve-local target to start the database automatically.

    The generator adds a typed db property to ServiceContext in context.ts:

    packages/api/src/context.ts
    import { getPrisma as getMyDb } from ':my-scope/my-db';
    export interface ServiceContext {
    tracer: Tracer;
    logger: Logger;
    metrics: Metrics;
    myDb: Awaited<ReturnType<typeof getMyDb>>;
    }

    The Prisma client is instantiated inside lambdaHandler and passed through the service context:

    packages/api/src/handler.ts
    import { getPrisma as getMyDb } from ':my-scope/my-db';
    export const lambdaHandler = async (event: APIGatewayProxyEvent) => {
    const httpRequest = convertEvent(event);
    const myDb = await getMyDb();
    const httpResponse = await serviceHandler.handle(httpRequest, {
    tracer,
    logger,
    metrics,
    myDb,
    });
    return convertVersion1Response(httpResponse);
    };

    Access db from the context in your operation implementations:

    packages/api/src/operations/list-users.ts
    import { ListUsersOperationInput, ListUsersOperationOutput } from '../generated/ssdk/index.js';
    import { ServiceContext } from '../context.js';
    export const listUsers = async (
    input: ListUsersOperationInput,
    ctx: ServiceContext,
    ): Promise<ListUsersOperationOutput> => {
    const users = await ctx.myDb.user.findMany();
    return { users };
    };

    Running the generator again with a different target adds the second database alongside the first. Both clients are added to ServiceContext and instantiated in handler.ts:

    packages/api/src/context.ts
    export interface ServiceContext {
    tracer: Tracer;
    logger: Logger;
    metrics: Metrics;
    myDb: Awaited<ReturnType<typeof getMyDb>>;
    otherDb: Awaited<ReturnType<typeof getOtherDb>>;
    }
    packages/api/src/handler.ts
    const myDb = await getMyDb();
    const otherDb = await getOtherDb();
    const httpResponse = await serviceHandler.handle(httpRequest, {
    tracer,
    logger,
    metrics,
    myDb,
    otherDb,
    });

    To allow your API to connect to the database at runtime, the API Lambda functions must be deployed into the same VPC as the database and granted network and IAM access.

    In your application stack, deploy the API into the same VPC as the database, then call allowDefaultPortFrom and grantConnect to open the network path and grant IAM rds-db:connect permission to each Lambda handler:

    packages/infra/src/stacks/application-stack.ts
    import { MyDatabase } from ':my-scope/common-constructs';
    const db = new MyDatabase(this, 'Db', { vpc, ... });
    const api = new MyApi(this, 'Api', {
    integrations: MyApi.defaultIntegrations(this)
    .withDefaultOptions({
    vpc,
    vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS },
    })
    .build(),
    });
    Object.entries(api.integrations).forEach(([operation, integration]) => {
    db.allowDefaultPortFrom(integration.handler, `Allow ${operation} to connect to the database`);
    db.grantConnect(integration.handler);
    });

    Deploy the API Lambda functions into a private subnet with egress, not a private isolated subnet. At runtime, getPrisma() retrieves database connection details from AWS AppConfig, which is a public AWS service endpoint that requires outbound internet access.

    The generator applies the same Prisma client injection inside the request handler in local-server.ts:

    packages/api/src/local-server.ts
    import { getPrisma as getMyDb } from ':my-scope/my-db';
    const server = createServer(async function (req, res) {
    const httpRequest = convertRequest(req);
    const myDb = await getMyDb();
    const httpResponse = await serviceHandler.handle(httpRequest, {
    tracer,
    logger,
    metrics,
    myDb,
    });
    return writeResponse(httpResponse, res);
    });
    Terminal window
    pnpm nx serve-local <api-project-name>

    This starts both the API and the local database. The SERVE_LOCAL=true environment variable is set automatically, so the Prisma client connects to the local Docker database instead of Aurora.