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.
Prerequisites
Section titled “Prerequisites”Before using this generator, ensure you have:
- A
ts#smithy-apiproject (TypeScript backend) - A
ts#rdbproject
Run the Generator
Section titled “Run the Generator”- Install the Nx Console VSCode Plugin if you haven't already
- Open the Nx Console in VSCode
- Click
Generate (UI)in the "Common Nx Commands" section - Search for
@aws/nx-plugin - connection - Fill in the required parameters
- Click
Generate
pnpm nx g @aws/nx-plugin:connectionyarn nx g @aws/nx-plugin:connectionnpx nx g @aws/nx-plugin:connectionbunx nx g @aws/nx-plugin:connectionYou can also perform a dry-run to see what files would be changed
pnpm nx g @aws/nx-plugin:connection --dry-runyarn nx g @aws/nx-plugin:connection --dry-runnpx nx g @aws/nx-plugin:connection --dry-runbunx nx g @aws/nx-plugin:connection --dry-runSelect your Smithy API backend project as the source and your relational database project as the target.
Options
Section titled “Options”| 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. |
Generator Output
Section titled “Generator Output”The generator modifies three existing files in your Smithy API backend:
Directorypackages/api/src
- context.ts
dbproperty added toServiceContext - handler.ts Prisma client created inside
lambdaHandler, passed toserviceHandler.handle - local-server.ts Prisma client created inside the request handler, passed to
serviceHandler.handle
- context.ts
Additionally, it updates the API’s serve-local target to start the database automatically.
How It Works
Section titled “How It Works”ServiceContext
Section titled “ServiceContext”The generator adds a typed db property to ServiceContext in 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>>;}Lambda Handler
Section titled “Lambda Handler”The Prisma client is instantiated inside lambdaHandler and passed through the service context:
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);};Using the Database in Operations
Section titled “Using the Database in Operations”Access db from the context in your operation implementations:
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 };};Multiple Databases
Section titled “Multiple Databases”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:
export interface ServiceContext { tracer: Tracer; logger: Logger; metrics: Metrics; myDb: Awaited<ReturnType<typeof getMyDb>>; otherDb: Awaited<ReturnType<typeof getOtherDb>>;}const myDb = await getMyDb();const otherDb = await getOtherDb();const httpResponse = await serviceHandler.handle(httpRequest, { tracer, logger, metrics, myDb, otherDb,});Infrastructure
Section titled “Infrastructure”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:
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.
Pass the database module outputs into your API module so it can reach the database and read its runtime configuration:
module "my_database" { source = "../../common/terraform/src/app/dbs/my-database" vpc_id = module.vpc.vpc_id database_subnet_ids = module.vpc.private_isolated_subnet_ids lambda_subnet_ids = module.vpc.private_subnet_ids}
module "api" { source = "..." vpc_id = module.vpc.vpc_id private_subnet_ids = module.vpc.private_subnet_ids
appconfig_application_id = module.my_database.appconfig_application_id database_cluster_resource_id = module.my_database.cluster_resource_id database_runtime_user = module.my_database.database_runtime_user database_security_group_id = module.my_database.security_group_id database_port = module.my_database.cluster_port
environment_variables = { RUNTIME_CONFIG_APP_ID = module.my_database.appconfig_application_id }}Deploy the API Lambda functions into private subnets with egress, not private isolated subnets. Ensure the API Lambda role has rds-db:connect permission and that its security group can reach the database security group on the database port.
Local Development
Section titled “Local Development”The generator applies the same Prisma client injection inside the request handler in 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);});pnpm nx serve-local <api-project-name>yarn nx serve-local <api-project-name>npx nx serve-local <api-project-name>bunx 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.