6.9 KiB
6.9 KiB
Deployment
Production Build
Compile to Binary (Recommended)
bun build \
--compile \
--minify-whitespace \
--minify-syntax \
--target bun \
--outfile server \
src/index.ts
Benefits:
- No runtime needed on deployment server
- Smaller memory footprint (2-3x reduction)
- Faster startup
- Single portable executable
Run the binary:
./server
Compile to JavaScript
bun build \
--minify-whitespace \
--minify-syntax \
--outfile ./dist/index.js \
src/index.ts
Run:
NODE_ENV=production bun ./dist/index.js
Docker
Basic Dockerfile
FROM oven/bun:1 AS build
WORKDIR /app
# Cache dependencies
COPY package.json bun.lock ./
RUN bun install
COPY ./src ./src
ENV NODE_ENV=production
RUN bun build \
--compile \
--minify-whitespace \
--minify-syntax \
--outfile server \
src/index.ts
FROM gcr.io/distroless/base
WORKDIR /app
COPY --from=build /app/server server
ENV NODE_ENV=production
CMD ["./server"]
EXPOSE 3000
Build and Run
docker build -t my-elysia-app .
docker run -p 3000:3000 my-elysia-app
With Environment Variables
FROM gcr.io/distroless/base
WORKDIR /app
COPY --from=build /app/server server
ENV NODE_ENV=production
ENV PORT=3000
ENV DATABASE_URL=""
ENV JWT_SECRET=""
CMD ["./server"]
EXPOSE 3000
Cluster Mode (Multiple CPU Cores)
// src/index.ts
import cluster from 'node:cluster'
import os from 'node:os'
import process from 'node:process'
if (cluster.isPrimary) {
for (let i = 0; i < os.availableParallelism(); i++) {
cluster.fork()
}
} else {
await import('./server')
console.log(`Worker ${process.pid} started`)
}
// src/server.ts
import { Elysia } from 'elysia'
new Elysia()
.get('/', () => 'Hello World!')
.listen(3000)
Environment Variables
.env File
NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://user:password@localhost:5432/db
JWT_SECRET=your-secret-key
CORS_ORIGIN=https://example.com
Load in App
import { Elysia } from 'elysia'
const app = new Elysia()
.get('/env', () => ({
env: process.env.NODE_ENV,
port: process.env.PORT
}))
.listen(parseInt(process.env.PORT || '3000'))
Platform-Specific Deployments
Railway
// Railway assigns random PORT via env variable
new Elysia()
.get('/', () => 'Hello Railway')
.listen(process.env.PORT ?? 3000)
Vercel
// src/index.ts
import { Elysia } from 'elysia'
export default new Elysia()
.get('/', () => 'Hello Vercel')
export const GET = app.fetch
export const POST = app.fetch
// vercel.json
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"bunVersion": "1.x"
}
Cloudflare Workers
import { Elysia } from 'elysia'
import { CloudflareAdapter } from 'elysia/adapter/cloudflare-worker'
export default new Elysia({
adapter: CloudflareAdapter
})
.get('/', () => 'Hello Cloudflare!')
.compile()
# wrangler.toml
name = "elysia-app"
main = "src/index.ts"
compatibility_date = "2025-06-01"
Node.js Adapter
import { Elysia } from 'elysia'
import { node } from '@elysiajs/node'
const app = new Elysia({ adapter: node() })
.get('/', () => 'Hello Node.js')
.listen(3000)
Performance Optimization
Enable AoT Compilation
new Elysia({
aot: true // Ahead-of-time compilation
})
Use Native Static Response
new Elysia({
nativeStaticResponse: true
})
.get('/version', 1) // Optimized for Bun.serve.static
Precompile Routes
new Elysia({
precompile: true // Compile all routes ahead of time
})
Health Checks
new Elysia()
.get('/health', () => ({
status: 'ok',
timestamp: Date.now()
}))
.get('/ready', ({ db }) => {
// Check database connection
const isDbReady = checkDbConnection()
if (!isDbReady) {
return status(503, { status: 'not ready' })
}
return { status: 'ready' }
})
Graceful Shutdown
import { Elysia } from 'elysia'
const app = new Elysia()
.get('/', () => 'Hello')
.listen(3000)
process.on('SIGTERM', () => {
console.log('SIGTERM received, shutting down gracefully')
app.stop()
process.exit(0)
})
process.on('SIGINT', () => {
console.log('SIGINT received, shutting down gracefully')
app.stop()
process.exit(0)
})
Monitoring
OpenTelemetry
import { opentelemetry } from '@elysiajs/opentelemetry'
new Elysia()
.use(opentelemetry({
serviceName: 'my-service',
endpoint: 'http://localhost:4318'
}))
Custom Logging
.onRequest(({ request }) => {
console.log(`[${new Date().toISOString()}] ${request.method} ${request.url}`)
})
.onAfterResponse(({ request, set }) => {
console.log(`[${new Date().toISOString()}] ${request.method} ${request.url} - ${set.status}`)
})
SSL/TLS (HTTPS)
import { Elysia, file } from 'elysia'
new Elysia({
serve: {
tls: {
cert: file('cert.pem'),
key: file('key.pem')
}
}
})
.get('/', () => 'Hello HTTPS')
.listen(3000)
Best Practices
-
Always compile to binary for production
- Reduces memory usage
- Smaller deployment size
- No runtime needed
-
Use environment variables
- Never hardcode secrets
- Use different configs per environment
-
Enable health checks
- Essential for load balancers
- K8s/Docker orchestration
-
Implement graceful shutdown
- Handle SIGTERM/SIGINT
- Close connections properly
-
Use cluster mode
- Utilize all CPU cores
- Better performance under load
-
Monitor your app
- Use OpenTelemetry
- Log requests/responses
- Track errors
Example Production Setup
// src/server.ts
import { Elysia } from 'elysia'
import { cors } from '@elysiajs/cors'
import { opentelemetry } from '@elysiajs/opentelemetry'
export const app = new Elysia({
aot: true,
nativeStaticResponse: true
})
.use(cors({
origin: process.env.CORS_ORIGIN || 'http://localhost:3000'
}))
.use(opentelemetry({
serviceName: 'my-service'
}))
.get('/health', () => ({ status: 'ok' }))
.get('/', () => 'Hello Production')
.listen(parseInt(process.env.PORT || '3000'))
// Graceful shutdown
process.on('SIGTERM', () => {
app.stop()
process.exit(0)
})
// src/index.ts (cluster)
import cluster from 'node:cluster'
import os from 'node:os'
if (cluster.isPrimary) {
for (let i = 0; i < os.availableParallelism(); i++) {
cluster.fork()
}
} else {
await import('./server')
}
# Dockerfile
FROM oven/bun:1 AS build
WORKDIR /app
COPY package.json bun.lock ./
RUN bun install
COPY ./src ./src
ENV NODE_ENV=production
RUN bun build --compile --outfile server src/index.ts
FROM gcr.io/distroless/base
WORKDIR /app
COPY --from=build /app/server server
ENV NODE_ENV=production
CMD ["./server"]
EXPOSE 3000