A serverless notes API: ASP.NET Core 8 running as a single AWS Lambda behind API Gateway, with Cognito authentication, DynamoDB storage, and presigned S3 URLs for attachments.
Everything is declared in one SAM template — src/API/NotesApp/serverless.template — so a deploy creates the API, the user pool, the table, and the bucket together.
-
AWS credentials configured locally (
aws configure). The project defaults to thedefaultprofile and regioneu-west-1— seesrc/API/NotesApp/aws-lambda-tools-defaults.json. -
An S3 bucket in the deploy region to hold the packaged Lambda artifact. This is the deployment bucket, separate from the attachments bucket the template creates.
-
The Amazon.Lambda.Tools global tool:
dotnet tool install -g Amazon.Lambda.ToolsIf it is already installed, make sure it is current:
dotnet tool update -g Amazon.Lambda.Tools
cd src/API/NotesApp
dotnet lambda deploy-serverless --stack-name notes-app --resolve-s3 true
The same command handles updates — re-run it after code or template changes and CloudFormation applies a changeset to the existing stack.
To deploy to another region, pass --region and use a deployment bucket in that region. The template reads AWS::Region for its own environment variables, so no template edit is needed.
When the deploy finishes, the tool prints the stack outputs:
| Output | Use it for |
|---|---|
ApiURL |
Base URL of the Prod stage, e.g. https://abc123.execute-api.eu-west-1.amazonaws.com/Prod |
UserPoolClientId |
Cognito app client ID used to obtain tokens |
UserPoolId |
Needed for admin operations such as confirming a user |
You can re-read them at any time:
aws cloudformation describe-stacks --stack-name notes-app --region eu-west-1 --query "Stacks[0].Outputs"
Copy src/API/NotesApp/http-client.env.template.json to http-client.private.env.json (gitignored) and fill in host from ApiURL — without the trailing slash — plus clientId, email, and password.
Sign up through request 1 in src/API/NotesApp/cognito_api.http, then either confirm with the emailed code (request 2) or confirm directly:
aws cognito-idp admin-confirm-sign-up --region eu-west-1 --user-pool-id <UserPoolId> --username <email>
Run 3. Login in cognito_api.http to capture a token, then exercise the endpoints in notes_api.http. Select the dev environment in the IDE first so the private env values load.
AspNetCoreFunction— the Lambda (dotnet8, 512 MB, 30 s timeout) withANY /{proxy+}andANY /routes, fronted by a Cognito authorizer applied to every route by defaultNotesUserPool/NotesUserPoolClient— email-based sign-in, no client secret,USER_PASSWORD_AUTHenabled for local testingNotesTable— on-demand DynamoDB table,userId(HASH) +noteId(RANGE)NoteAttachmentsBucket— private S3 bucket with all public access blocked
Runtime configuration is injected by the template as environment variables — NotesTableName, S3BucketName, Cognito__UserPoolId, Cognito__Region, AmazonOptions__RegionEndpoint — so the deployed function never reads the placeholder values in appsettings.json.
cd src/API/NotesApp
dotnet run
The app listens on https://localhost:53018. Local runs read appsettings.Development.json (gitignored), so fill in Cognito:UserPoolId, S3BucketName, and the region there — pointing at a deployed stack's resources is the simplest option. Your AWS profile's credentials are used for DynamoDB and S3.
To exercise the Lambda adapter itself rather than Kestrel, install the mock tool and use the Mock Lambda Test Tool launch profile:
dotnet tool install -g Amazon.Lambda.TestTool-8.0
aws s3 rm s3://<attachments-bucket> --recursive
dotnet lambda delete-serverless --stack-name notes-app
Empty the attachments bucket first — CloudFormation cannot delete a non-empty bucket, and the stack deletion will otherwise fail partway through.