DEV Community

Vinicius Senger
Vinicius Senger

Posted on

Goodbye localhost: why Quarkus + Vanilla JS + AWS Lambda + DynamoDB Is the Fastest Path from Idea to Production

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:dev gives 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 mvnw wrapper)
  • 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
Enter fullscreen mode Exit fullscreen mode

Build the Lambda-ready artifact:

./build-lambda.sh
Enter fullscreen mode Exit fullscreen mode

This script does three things:

  1. Runs mvn clean package with the uber-jar profile
  2. Renames the runner JAR to function.jar
  3. 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
Enter fullscreen mode Exit fullscreen mode

Key things to notice:

  • HttpApi (API Gateway v2) — cleaner URLs with no /Prod stage prefix
  • PAY_PER_REQUEST billing — 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

This creates a samconfig.toml so future deploys are just:

sam build && sam deploy
Enter fullscreen mode Exit fullscreen mode

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/
---------------------------------------------------------------------------
Enter fullscreen mode Exit fullscreen mode

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"
  }'
Enter fullscreen mode Exit fullscreen mode

Open the UI:

open https://abc123xyz.execute-api.us-east-1.amazonaws.com/ui
Enter fullscreen mode Exit fullscreen mode

Step 6: Update and Redeploy

Made changes? Redeploy in under a minute:

./build-lambda.sh
sam build && sam deploy
Enter fullscreen mode Exit fullscreen mode

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│
                                     └─────────────┘
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

Then redeploy:

sam build && sam deploy
Enter fullscreen mode Exit fullscreen mode

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)