This is a starter kit for a modern fullstack webapp on AWS. Features including:
- ✅ Next.js app with both server / client side rendering
- ✅ tRPC backend for end-to-end type safety
- ✅ Prisma to access your relational database easily
- ✅ React Hook Form with tRPC validation
- ✅ Lambda with Lambda Web Adapter for true serverless computing
- ✅ CloudFront for caching
- ✅ AWS CDK for one-click deployment
This sample deploys your Next.js application to AWS Lambda container environment. With AWS Lambda Web Adapter, you can run the app transparently with Lambda and API Gateway pattern.
The advantage of using AWS Lambda is that it is fully serverless; you only pay when it received the actual requests. It is especially ideal for prototyping phase because you can use it with the minimal cost. At the same time, it is also ideal for production workload because it scales out in a few seconds according to the amount of requests.
All of the infrastructure parts are deployed in a single command with AWS Cloud Development Kit, allowing you to try this sample effortlessly.
You need the following tools to deploy this sample:
Then run the following commands:
cd cdk
npm ci
npx cdk bootstrap
npx cdk deploy
Initial deployment usually takes about 20 minutes. You can also use npx cdk deploy
command to deploy the app when you make changes to the code.
After a successful deployment, you will get a CLI output like the below:
✅ CdkStack
✨ Deployment time: 940.79s
Outputs:
CdkStack.DatabaseBastionHostBastionHostId1600F37C = i-0xxxxxxxxxxxxxx
CdkStack.DatabaseDatabaseSecretsCommandF4A622EB = aws secretsmanager get-secret-value --secret-id DatabaseClusterSecretD1FB63-xxxxxxxx --region ap-northeast-1
CdkStack.DatabasePortForwardCommandC3718B89 = aws ssm start-session --region ap-northeast-1 --target i-0xxxxxxxxxxxxxx --document-name AWS-StartPortForwardingSessionToRemoteHost --parameters '{"portNumber":["3306"], "localPortNumber":["3306"], "host": ["cdkstack-databasecluster5b53a178-4ont9k5raja4.cluster-xxxxxxxxx.ap-northeast-1.rds.amazonaws.com"]}'
CdkStack.MigrationFunctionName = CdkStack-WebAppMigrationRunner4FF0034F-wpE8B46If1um
CdkStack.WebAppCloudFrontUrlBAD6B277 = https://xxxxxxxxxxxx.cloudfront.net
CdkStack.WebAppMigrationCommandA625F170 = aws lambda invoke --function-name CdkStack-WebAppMigrationRunner4FF0034F-wpE8B46If1um --cli-binary-format raw-in-base64-out --payload '{"command":"deploy"}' response.json
Stack ARN:
arn:aws:cloudformation:ap-northeast-1:123456789012:stack/CdkStack/ecf45e70-a0a5-11ee-8ca1-0a8ebf670439
You can now try the sample app on your browser by opening the URL in CdkStack.WebAppCloudFrontUrlBAD6B277
output. Let's try to post some comments.
You can use this sample as a starter kit to develop a fullstack serverless webapp. Here are some tips to interact with this sample.
By default, a database migration is executed every time you run cdk deploy
during CloudFormation deployment.
You can disable the behavior by commenting out the below code in webapp.ts:
+ // comment out the trigger
+ /*
const trigger = new Trigger(this, "MigrationTrigger", {
handler: migration,
});
trigger.node.addDependency(database.cluster);
+ */
Instead you can run a database migration manually. This is especially ideal if you want to separate a DB migration process from application deployment.
You can invoke the migration Lambda handler by the following command (CdkStack.WebAppMigrationCommandA625F170
in the stack CLI output):
aws lambda invoke --function-name CdkStack-WebAppMigrationRunner4FF0034F-wpE8B46If1um --cli-binary-format raw-in-base64-out --payload '{"command":"deploy"}' response.json
Or you can run any SQL query from the bastion server by SSM port forwarding (CdkStack.DatabasePortForwardCommandC3718B89
):
aws ssm start-session --target i-xxxxxx --document-name AWS-StartPortForwardingSessionToRemoteHost --parameters '{"portNumber":["3306"], "localPortNumber":["3306"], "host": ["cdkstack-databasecluster5b53a178-4ont9k5raja4.cluster-xxxxxx.ap-northeast-1.rds.amazonaws.com"]}'
The connection info for the Aurora database can be retrieved with the following command (CdkStack.DatabaseDatabaseSecretsCommandF4A622EB
):
aws secretsmanager get-secret-value --secret-id DatabaseClusterSecretD1FB63-xxxxxx --region ap-northeast-1
Then you can connect your Aurora server by any tools like Sequel Ace, or by the following command:
mysql -u admin -p [PASSWORD]
Because this sample uses a standard directory structure following tRPC official documents, you can basically follow the document to make changes to tRPC implementation.
The quick summary about where each tRPC definition example files is:
- server.ts: tRPC app router definition
- routers/**/index.ts: the definition of each child router
- common/types: the zod definition of router input, which can be referenced from both backend and frontend
- components/post.tsx: example implementation of tRPC client with React Hook Form
To save Lambda cost and increase performance, it is important to use CloudFront to deliver contents that can be cached without accessing Lambda origin.
By default caching is enabled in some path patterns. Open webapp.ts and you can find this line:
const cachedPathPatterns = ['/_next/static/*', '/favicon.ico', '/api/trpc/*'];
Add any path patterns you want to enable cache in CloudFront.
To actually use cache in CloudFront, you have to send Cache-Control
response header from your app. See this document for more details: Managing how long content stays in the cache (expiration).
Files in _next/static
path should have appropriate Cache-Control
headers by default.
For tRPC paths, you can set Cache-Control
header in [trpc].ts
selectively for each path.
For Next.js public files, you can set Cache-Control
header in next.config.js
for each file.
To avoid incurring future charges, clean up the resources when you no longer need them.
You can remove the AWS resources deployed by this sample running the following command:
cd cdk
npx cdk destroy --force
See CONTRIBUTING for more information.
This library is licensed under the MIT-0 License. See the LICENSE file.