A production-ready ElysiaJS starter for your projects, featuring
Clean Architecture, Better Auth, Drizzle ORM, and OpenAPI documentation.
- β‘ High-performance, fully async APIs
- π Built-in Auth & API Endpoint Protection (Better Auth)
- β Type-safe validation (TypeBox)
- ποΈ Database & Migrations (PostgreSQL + Drizzle ORM)
- π API Documentation via OpenAPI with Scalar UI
- π¦ Rate limiter for global & auth
- π§Ύ Structured logging using Pino
- π Configurable CORS middleware for frontend integration
- π§ Email Infrastructure via Resend + React Email (optional)
- π³ One-command Docker Compose
Elysia.js is fast. It's currently one of the fastest frameworks in the Bun ecosystem, with benchmarks showing performance that can match Golang and Rust frameworks (based on TechEmpower Benchmarks).
The problem? Setting up Authentication, ORM, Docker, and logging from scratch for a production-ready app takes hours.
This boilerplate provides a simple, ready-to-use, production-grade foundation so you can focus on building features immediately with Elysia, without redoing repetitive configuration and setup.
- Build a production-ready API on Elysia.js without reinventing the same setup
- Start developing immediately with sensible defaults and clear structure
- Keep full control over configuration while avoiding boilerplate fatigue
- Use modern, type-safe tooling without framework lock-in
Click the green "Use this template" button at the top of this repo β or use the direct link:
π Create from template
Creates a clean repo without git history.
If you prefer cloning manually:
git clone https://github.com/techfusionid/elysia-production-template.git
cd elysia-production-templateSetup environment:
cp .env.example .envImportant
Open .env and update BETTER_AUTH_SECRET and DATABASE_URL before running.
Install & run:
bun install
bun run devOr with Docker:
docker compose upYour app is now running:
- API: http://localhost:3000
- Docs: http://localhost:3000/docs
- Health: http://localhost:3000/health
src/
βββ common/
β βββ config/ # Environment, auth, email settings
β βββ db/ # Database connection & schema
β βββ logger/ # Pino logger
β βββ middleware/ # Auth guard, rate limiter, request logger
βββ modules/
β βββ auth/ # Better Auth integration
β βββ health/ # Health check endpoint
β βββ posts/ # Example CRUD module
βββ app.ts # App composition & middleware
βββ index.ts # Entry point
tests/ # Integration tests
π Note The
postsmodule and its API endpoints are provided as example CRUD implementations. You can safely modify or remove them if not needed.
Key environment variables (see .env.example for full list):
| Variable | Description | Required |
|---|---|---|
NODE_ENV |
Runtime environment (development, production, test) |
No (default: development) |
HOST |
Server bind address | No (default: 0.0.0.0) |
PORT |
Server port | No (default: 3000) |
DATABASE_URL |
PostgreSQL connection string | Yes |
BETTER_AUTH_SECRET |
Auth secret key (generate: openssl rand -base64 32) |
Yes |
BETTER_AUTH_URL |
Base URL for auth callbacks | Yes |
ENABLE_AUTH |
Enable/disable auth module | No (default: true) |
REQUIRE_EMAIL_VERIFICATION |
Require email verification before login | No (default: false) |
ENABLE_RATE_LIMITER |
Enable/disable rate limiting | No (default: true) |
LOG_LEVEL |
Log level: fatal, error, warn, info, debug, trace |
No (default: info) |
CORS_ORIGIN |
Allowed origins (comma-separated) | No (default: http://localhost:3000,http://localhost:5173) |
NODE_ENVis used to adjust logging visual, testing, and runtime behavior.
Logging behavior is automatically adjusted based on NODE_ENV:
NODE_ENV=development: human-readable logs for easier debuggingNODE_ENV=production: structured JSON logs, optimized for log aggregation and monitoring
Log verbosity can be controlled using the LOG_LEVEL environment variable.
Development:
bun run dev # Start dev server with hot reloadπ³ Local Development with Docker PostgreSQL
For local development, it's recommended to run PostgreSQL via Docker while keeping the API running locally with Bun.
docker compose up -d postgresProduction:
bun run build # Build for production
bun run start # Start production serverDocker Compose
Run API + PostgreSQL fully inside Docker:
docker compose up
docker compose up --build
docker compose downView compose logs:
docker compose logs -f
docker compose logs -f api
docker compose logs -f postgresDatabase & Migration (Drizzle):
bun run db:generate # Generate Drizzle migrations
bun run db:migrate # Run migrations
bun run db:studio # Open Drizzle Studio (visual database browser)Testing:
bun run test # Run integration testsNote
Tests run against your local database. Make sure PostgreSQL is running before testing.
Linting:
bun run lint # Run Biome linter
bun run format # Format codeBelow are the main routes exposed by the template. See /docs for full request/response schemas.
Auth routes (via Better Auth):
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/auth/sign-up/email |
Register |
| POST | /api/auth/sign-in/email |
Login |
| POST | /api/auth/sign-out |
Logout |
| GET | /api/auth/get-session |
Get current session |
| POST | /api/auth/request-password-reset |
Request password reset |
| POST | /api/auth/reset-password |
Reset password |
Posts routes (example CRUD β safe to remove):
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /api/posts |
List all posts | No |
| GET | /api/posts/:id |
Get single post | No |
| POST | /api/posts |
Create post | Yes |
| PUT | /api/posts/:id |
Update post | Owner |
| DELETE | /api/posts/:id |
Delete post | Owner |
Health:
| Method | Endpoint | Description |
|---|---|---|
| GET | /health |
Health check with DB status |
Full API documentation available at
/docswhen running.
The posts module is included as a reference CRUD implementation. Feel free to remove or replace it with your own modules.
To add a new module:
- Create folder:
src/modules/your-module/ - Add
index.ts(routes),service.ts(business logic), and optionallyschema.ts - Register in
src/app.ts:
import { yourModule } from "@modules/your-module";
app.use(yourModule);This template is designed to be configurable without hiding behavior. Below are common customization points.
To disable authentication, set ENABLE_AUTH=false in .env.
All auth routes and middleware will be automatically excluded.
If you want to completely remove built-in auth from your codebase:
- Delete the
src/modules/auth/folder - Remove the
src/common/config/auth.tsfile - (Optional) Clean up any route guards that use
auth: true(e.g., inpostsmodule)
π‘ For most use cases, just setting
ENABLE_AUTH=falseis sufficient and safe.
By default, email verification is disabled. To enable, set in .env:
REQUIRE_EMAIL_VERIFICATION=trueWhen enabled, users must verify their email before they can log in.
Tip
In development, emails are logged to console (no provider needed). To test with real emails, configure Resend (see below).
To send real emails for verification and password reset:
- Create an account at resend.com
- Get your API key from the dashboard
- Add to
.env:RESEND_API_KEY=re_xxxxxxxxxxxxx EMAIL_FROM=onboarding@resend.dev
Note
Sandbox mode: onboarding@resend.dev can only send to your Resend account email. For production, verify your domain. See Resend documentation for details.
Password reset works out of the box. Reset links are logged to console in development, or sent via email when Resend is configured.
Rate limiting is enabled by default. To disable (e.g., for testing):
ENABLE_RATE_LIMITER=falseAdjust rate limits via environment variables:
# Global rate limit
RATE_LIMIT_WINDOW_MS=60000 # 60 seconds
RATE_LIMIT_MAX=100 # 100 requests per window
# Auth-specific rate limit (stricter)
AUTH_RATE_LIMIT_WINDOW_MS=60000
AUTH_RATE_LIMIT_MAX=10Use the auth route option to mark endpoints as protected:
// Public route - anyone can access
.get("/", async () => { ... })
// Protected route - requires login
.post("/", async ({ user }) => { ... }, { auth: true })Uses drizzle-typebox to generate validation schemas from Drizzle ORM:
// Auto-generated from Drizzle + override validation
export const createPostSchema = createInsertSchema(posts, {
title: t.String({ minLength: 5, maxLength: 50 }),
content: t.String({ minLength: 10 }),
});
// Use in routes (omit auto-generated fields)
body: t.Omit(createPostSchema, ['id', 'authorId', 'createdAt', 'updatedAt'])When you add fields in Drizzle, they auto-include in validation. Only validation rules need manual override.
For additional checks beyond login (e.g., ownership, roles, permissions), add logic in the route handler. The posts module shows an ownership check example:
.put("/:id", async ({ params, user, set }) => {
const isOwner = await service.isPostOwner(params.id, user.id);
if (!isOwner) {
set.status = 403;
return { error: "Forbidden", message: "Not allowed" };
}
// proceed...
}, { auth: true })This template is container-ready and works well with most Docker-based platforms.
Thanks for your interest in contributing to Elysia Production Template! Contributions of all kinds are welcome: bug fixes, improvements, documentation, and examples.
This project aims to stay simple, production-focused, and easy to extend, so we appreciate well-scoped and thoughtful contributions.
- Fork this repository
- Create a new branch from
main - Make your changes
- Run linting and tests using
bun test - Open a pull request with clear PR description of the changes
git checkout -b my-featureWhy Elysia.js?
Elysia is currently the fastest Bun framework with excellent TypeScript support, end-to-end type safety, and a clean plugin architecture. Perfect for building production APIs without sacrificing developer experience.
Can I use a different database?
The template uses PostgreSQL with Drizzle ORM. Drizzle also supports MySQL and SQLite. Update the connection config and adjust schema types as needed.
Is this production-ready?
Yes. Includes rate limiting, structured logging, error handling, health checks, and Docker support. For production, ensure you:
- Use strong secrets
- Set up database backups
- Ensure HTTPS is handled by a reverse proxy or your hosting platform (nginx, Caddy, or managed TLS)