AI-assisted development has changed everything. You describe what you want, an AI builds it, and in a couple of hours you have a working app on your laptop. I built re:Money — a full financial tracking app with dashboards, pivot tables, CSV import, and a chat interface — using Kiro, and it was genuinely fun.
But here's the pattern I keep seeing: developers build something incredible with AI, demo it on localhost, and then... it stays on localhost. Deploying to production becomes the hard part. Suddenly you need Docker, Kubernetes, a managed database, VPC configs, CI/CD pipelines, and a cloud architecture degree.
It doesn't have to be that way.
The secret isn't just how you code — it's what you choose to build with. re:Money deploys to AWS Lambda with two commands because of four deliberate architectural choices: Quarkus as the framework, vanilla JavaScript for the front-end, AWS Lambda for compute, and DynamoDB for storage. Together, these four pieces made the gap between "works on my machine" and "running in production" almost zero.
But First — What Is "Vanilla JS"?
If you've never heard the term, there's a great backstory. In 2012, a satirical website called Vanilla-JS.com appeared, presenting Vanilla JS as a "fast, lightweight framework" already "used by more websites than jQuery and React combined." When you clicked to "download" the library, you got an empty file — because Vanilla JS is just JavaScript itself. No framework. No dependencies. Just the language your browser already speaks.
That joke carries a real insight: for many applications, plain JavaScript is all you need. And when it comes to deployment, what you don't depend on matters just as much as what you use.
Why This Stack Is a Serverless Superpower
Before we get into the deployment steps, let's talk about why each piece of this stack is uniquely suited for going from idea to production.
Quarkus: One Framework, One Artifact, Zero Friction
Quarkus is the glue that holds everything together. It gives you:
- Server-side rendering with Qute — HTML templates that live inside the JAR, no separate front-end build
- First-class AWS Lambda support — Quarkus has a dedicated Lambda extension that handles the HTTP adapter for you
- Fast startup — designed from the ground up for cloud-native and serverless workloads
- Single uber-JAR — your entire app (REST API, templates, CSS, JS) compiles into one artifact
-
Dev mode with hot reload —
./mvnw quarkus:devgives you instant feedback during development
With Spring Boot, you'd need extra configuration for Lambda. With Quarkus, it's a dependency and a handler class. Done.
Vanilla JS: No Build Pipeline for the Front-End
With React or Angular, you need Node.js, npm, a bundler (Webpack/Vite), and a separate build step that produces static assets you then have to serve from S3 or CloudFront. With re:Money, the UI is Qute templates + vanilla JS. It's all inside the JAR. One build, one artifact, one deployment.
AWS Lambda: Serverless Compute That Scales to Zero
Lambda means:
- No servers to manage — no EC2 instances, no ECS clusters, no Kubernetes
- Pay only for what you use — billed per request and compute time, idle = free
- Automatic scaling — from zero to thousands of concurrent requests without configuration
-
SAM makes it declarative — your entire infrastructure is defined in one
template.yaml
Lambda is the reason sam build && sam deploy is all you need. No Docker registry, no container orchestration, no load balancers.
DynamoDB: The Only Database That Matches Lambda's Model
This is the big one. If re:Money used PostgreSQL or MySQL, you'd need:
- An RDS instance (always running, always billing)
- VPC configuration for Lambda to reach the database
- Connection pooling (Lambda cold starts + relational DBs = pain)
- Database migrations on every deploy
DynamoDB has none of these problems. It's:
- Pay-per-request — zero cost when idle
- No connections to manage — it's HTTP-based, perfect for Lambda's ephemeral nature
- No VPC required — Lambda talks to DynamoDB over the public AWS API
- No migrations — schema-free, so your code is your schema
- Scales to zero and to infinity — just like Lambda itself
Lambda + DynamoDB is the only truly serverless compute + storage combo on AWS. Everything else requires something running 24/7.
The Four Pieces Working Together
Here's why these four choices compound:
- Quarkus bundles your templates and JS into a single JAR → no separate front-end deployment
- Vanilla JS means no Node.js build step → that JAR is all you need to build
- Lambda runs that JAR serverlessly → no infrastructure to provision
- DynamoDB needs no VPC or connections → Lambda talks to it out of the box
Remove any one piece and the deployment story gets harder. Together, they create a frictionless path from git clone to production.
Single Deployable Artifact
re:Money compiles to one function.zip. No Docker images, no container registries, no multi-stage builds. SAM uploads it to S3, Lambda runs it. That's it.
The Cost Equation
For a personal finance app (or any low-to-moderate traffic tool), this architecture can run for pennies per month:
| Service | Cost |
|---|---|
| Lambda | Free tier: 1M requests/month |
| API Gateway | Free tier: 1M calls/month |
| DynamoDB | Free tier: 25GB storage, 25 WCU/RCU |
| Total | ~$0/month for personal use |
Try getting that with ECS + RDS.
Prerequisites
Before starting, make sure you have:
- Java 21+ installed
-
Maven 3.8+ (or use the included
mvnwwrapper) -
AWS CLI configured with credentials (
aws configure) - AWS SAM CLI installed (install guide)
Step 1: Clone and Build
git clone https://github.com/renascence-computing/reos-money.git
cd reos-money
Build the Lambda-ready artifact:
./build-lambda.sh
This script does three things:
- Runs
mvn clean packagewith the uber-jar profile - Renames the runner JAR to
function.jar - Zips it into
target/function.zip
That's your entire application — backend, front-end, templates, CSS, JavaScript — in one zip file.
Step 2: Review the SAM Template
The template.yaml defines everything Lambda needs:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: re:Money Quarkus Lambda Application
Resources:
ReMoneyFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: target/function.zip
Handler: io.quarkus.amazon.lambda.runtime.QuarkusStreamHandler::handleRequest
Runtime: java21
Timeout: 30
MemorySize: 1024
Events:
ReMoneyApi:
Type: HttpApi
Properties:
Path: /{proxy+}
Method: ANY
ReMoneyRoot:
Type: HttpApi
Properties:
Path: /
Method: ANY
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref EntryTable
EntryTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: entry
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
Key things to notice:
-
HttpApi(API Gateway v2) — cleaner URLs with no/Prodstage prefix -
PAY_PER_REQUESTbilling — you only pay for what you use -
/{proxy+}catches all routes and forwards them to Quarkus - DynamoDB table is created automatically — no manual setup needed
- No VPC, no subnets, no security groups — DynamoDB doesn't need them
Step 3: Deploy with SAM
For your first deployment, run the guided flow:
sam build
sam deploy --guided
SAM will ask you a few questions:
Stack Name [reMoney]: reMoney
AWS Region [us-east-1]: us-east-1
Confirm changes before deploy [y/N]: y
Allow SAM CLI IAM role creation [Y/n]: Y
ReMoneyFunction has no authentication. Is this okay? [y/N]: y
Save arguments to configuration file [Y/n]: Y
This creates a samconfig.toml so future deploys are just:
sam build && sam deploy
That's it. Two commands.
Step 4: Get Your API URL
After deployment, SAM prints your endpoint:
Outputs
---------------------------------------------------------------------------
Key ReMoneyApi
Description API Gateway endpoint URL for re:Money application
Value https://abc123xyz.execute-api.us-east-1.amazonaws.com/
---------------------------------------------------------------------------
Open that URL in your browser and you'll see re:Money running — dashboard, accounts, categories, pivot tables, everything.
Step 5: Verify It Works
Test the API:
# Get all entries
curl https://abc123xyz.execute-api.us-east-1.amazonaws.com/entryResource/findAll
# List categories
curl https://abc123xyz.execute-api.us-east-1.amazonaws.com/entryResource/listCategories
# Add an entry
curl -X POST https://abc123xyz.execute-api.us-east-1.amazonaws.com/entryResource \
-H "Content-Type: application/json" \
-d '{
"accountID": "checking",
"description": "Coffee",
"category": "Food",
"amount": -4.50,
"date": "2026-04-06T09:00"
}'
Open the UI:
open https://abc123xyz.execute-api.us-east-1.amazonaws.com/ui
Step 6: Update and Redeploy
Made changes? Redeploy in under a minute:
./build-lambda.sh
sam build && sam deploy
No Docker push, no CI/CD pipeline required, no container orchestration. Build, deploy, done.
Architecture Overview
Here's what you end up with:
┌──────────┐ ┌─────────────┐ ┌──────────────────┐ ┌──────────┐
│ Browser │────▶│ API Gateway │────▶│ Lambda (Java 21) │────▶│ DynamoDB │
│ │◀────│ │◀────│ Quarkus + Qute │◀────│ │
└──────────┘ └─────────────┘ └──────────────────┘ └──────────┘
│
┌──────┴──────┐
│ Single JAR │
│ - REST API │
│ - HTML/CSS │
│ - Vanilla JS│
└─────────────┘
Three managed AWS services. Zero servers. Zero containers. Zero infrastructure to maintain.
Why This Wouldn't Work with a Traditional Stack
Let's compare what deployment looks like with a typical modern stack:
| Concern | React + Spring Boot + PostgreSQL | re:Money (Quarkus + Vanilla JS + Lambda + DynamoDB) |
|---|---|---|
| Framework | Spring Boot (heavier cold starts) | Quarkus (Lambda-optimized, SnapStart-ready) |
| Front-end build | npm install && npm run build |
None (Qute templates + vanilla JS in JAR) |
| Front-end hosting | S3 + CloudFront | Served by Lambda (bundled in artifact) |
| Compute | ECS/EKS or Lambda with extra config | Lambda (native Quarkus support) |
| Database | RDS ($15+/month minimum) | DynamoDB (free tier) |
| VPC needed? | Yes (for RDS) | No |
| Connection pooling | Yes (RDS Proxy) | No (HTTP-based) |
| Cold start impact | High (Spring + DB connections) | Low (Quarkus + SnapStart, no DB pool) |
| Deploy commands | 5-10 steps | sam build && sam deploy |
| Monthly cost (low traffic) | $20-50+ | $0-1 |
The combination of Quarkus + vanilla JS + Lambda + DynamoDB isn't just a preference — it's an architectural decision that eliminates entire categories of deployment complexity.
Bonus: Eliminate Cold Starts with Lambda SnapStart
Java on Lambda has one well-known trade-off: cold starts. The first request after a period of inactivity can take a few seconds while the JVM initializes. Lambda SnapStart solves this.
What SnapStart Does
SnapStart takes a Firecracker microVM snapshot of your function after initialization — the JVM is booted, Quarkus is loaded, DynamoDB clients are ready. When a new invocation arrives, Lambda restores from that snapshot instead of starting from scratch. The result: cold starts drop from seconds to milliseconds.
How to Enable It
Add two lines to your template.yaml function properties:
ReMoneyFunction:
Type: AWS::Serverless::Function
Properties:
# ... existing properties ...
SnapStart:
ApplyOn: PublishedVersions
AutoPublishAlias: live
That's it. SnapStart tells Lambda to snapshot after init, and AutoPublishAlias creates a published version (required — SnapStart only works on published versions, not $LATEST).
You can also add this JVM flag to squeeze out extra startup performance:
Environment:
Variables:
JAVA_TOOL_OPTIONS: "-XX:+TieredCompilation -XX:TieredStopAtLevel=1"
Then redeploy:
sam build && sam deploy
Why This Matters
Without SnapStart, Java Lambda cold starts are typically 3-5 seconds for a Quarkus app. With SnapStart, you're looking at ~200-400ms — comparable to Node.js or Python cold starts, but with all the benefits of Java's type safety and Quarkus's performance at steady state.
Combined with DynamoDB (no connection pool to re-establish on restore), SnapStart makes Java on Lambda a genuinely production-ready choice with no latency excuses.
Tips for Production
- Custom domain: Add a custom domain via API Gateway custom domain names + Route 53
- Authentication: Add Amazon Cognito or a Lambda authorizer to protect your endpoints
- Cold starts: Enable SnapStart (see above) — it's the single biggest improvement you can make
- Monitoring: CloudWatch Logs are enabled by default — check the Lambda function's log group
- Backups: DynamoDB supports on-demand backups and point-in-time recovery — enable PITR for peace of mind
Wrapping Up
AI-assisted development gave us the power to build apps in hours. But the dirty secret is that most of those apps never leave localhost — not because the code is bad, but because the architecture wasn't chosen with deployment in mind.
re:Money works in production because of four choices made before writing a single line of code: Quarkus as the framework, vanilla JS for the front-end, Lambda for compute, and DynamoDB for storage. Those choices eliminated the entire deployment gap.
No node_modules. No database migrations. No VPC. No containers. No orchestration.
Just sam build && sam deploy.
Next time you're building something new with AI, think about where it needs to run — not just where it's being built. The best architecture is the one that makes production feel as easy as localhost.
re:Money was built with Kiro and is open source as part of the Renascence Computing Project. Try it out, deploy it to your own AWS account, and see how far you can go without a framework.
Top comments (0)