A full-stack serverless web app on AWS: a React single-page app served from S3 through CloudFront, a Lambda API behind the same CloudFront origin (so the browser never sees CORS), and a private Aurora Serverless v2 PostgreSQL database that only the function can reach.
The Pulumi program is split into two components:
components/database.ts- an Aurora Serverless v2 PostgreSQL cluster in private subnets, with its connection URL stored in AWS Secrets Manager.components/edge.ts- the S3 site bucket, the Lambda API (VPC-attached, with least-privilege access to the secret), and a CloudFront distribution that routes/api/*to the function and everything else to the SPA.
The app runs on top of the aws-ts-landing-zone example: it reads that stack's networkId, privateSubnetIds, and secretsStore outputs through a StackReference, so deploy the landing zone first.
- Install Pulumi
- Configure AWS credentials
- Install Node.js 20 or newer
- A deployed
aws-ts-landing-zonestack in the same account and region
-
Build the React SPA:
cd website npm install npm run build cd ..
-
Build the API bundle:
cd api npm install npm run build cd ..
-
Install the Pulumi program's dependencies:
npm install
-
Create a new stack:
pulumi stack init dev
-
Set the AWS region and point the stack at your landing zone (use the fully-qualified
<org>/<project>/<stack>name):pulumi config set aws:region us-west-2 pulumi config set landingZoneStack myorg/aws-ts-landing-zone/dev
-
Run
pulumi upto preview and deploy:pulumi up
-
Open the site.
pulumi upprints asiteUrl; the SPA fetches/api/randomand displays the number the API read from Postgres.curl "$(pulumi stack output apiUrl)/random"{"n":42}Note: A brand-new CloudFront distribution can take several minutes to finish deploying before the URL responds.
website/- Vite + React SPA, built towebsite/dist/.api/- Node 20 TypeScript handler, bundled toapi/dist/handler.jswith esbuild.index.ts- reads the landing-zone outputs, then instantiates theDatabaseandEdgecomponents.
To tear down the resources, run:
pulumi destroy
pulumi stack rmIdle cost is dominated by the Aurora Serverless v2 minimum capacity, the CloudFront distribution, and S3 storage. Lambda and the Function URL cost nothing when idle.
In this example you deployed a same-origin, serverless full-stack app on AWS - React on CloudFront + S3, a VPC-attached Lambda API, and a private Aurora Serverless v2 Postgres database - layered on a shared landing zone via a StackReference.