- Published on
Encore.ts — Infrastructure From Code, or How to Never Write Terraform Again
- Authors

- Name
- Sanjeev Sharma
- @webcoderspeed1
Introduction
Infrastructure-as-code tools like Terraform require learning HCL and managing state files. Encore.ts takes the opposite approach: declare infrastructure directly in your application code. The framework infers databases, APIs, services, and deployment requirements from TypeScript decorators. This guide covers building and deploying with Encore.
- What Encore.TS Does
- Declaring HTTP Endpoints
- TypeScript Auto-Migrations with @encore.db
- PubSub for Message Queues
- Cron Jobs
- Secrets Management
- Service-to-Service Calls with Type Safety
- Local Development with encore run
- Deploying to AWS, GCP, Azure
- Tracing and Metrics
- Environment Configuration
- When Encore Makes Sense
- Encore vs Serverless Framework
- Checklist
- Conclusion
What Encore.TS Does
Encore automatically manages:
- API endpoints via
@apidecorator - SQL databases via
@encore.dbwith auto-migrations - Message queues (pubsub) via
@encore.pubsub - Scheduled tasks via
@encore.cron - Secrets via
Encore.meta.environment - Service-to-service calls with automatic type safety
Your code is the source of truth. Encore generates CloudFormation, Terraform, or Kubernetes manifests.
Declaring HTTP Endpoints
The @api decorator exposes TypeScript functions as REST endpoints:
import { api } from 'encore.dev/api'
interface User {
id: number
name: string
email: string
}
export const getUser = api(
{ method: 'GET', path: '/users/:id', expose: true },
async (req: { id: number }): Promise<User> => {
return {
id: req.id,
name: 'Alice',
email: 'alice@example.com',
}
}
)
export const createUser = api(
{ method: 'POST', path: '/users', expose: true },
async (req: { name: string; email: string }): Promise<User> => {
return {
id: 1,
...req,
}
}
)
Encore infers the OpenAPI schema from parameter and return types. You get auto-generated docs, client SDKs, and type safety.
TypeScript Auto-Migrations with @encore.db
Declare databases without SQL:
import { db } from 'encore.dev/db'
interface User {
id: number
name: string
email: string
createdAt: Date
}
export const usersDB = db.createClient('users', {
migration: `
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
`,
})
export const createUser = api(
{ method: 'POST', path: '/users' },
async (req: { name: string; email: string }): Promise<User> => {
const result = await usersDB.query(
'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *',
[req.name, req.email]
)
return result.rows[0]
}
)
export const getUser = api(
{ method: 'GET', path: '/users/:id' },
async (req: { id: number }): Promise<User> => {
const result = await usersDB.query(
'SELECT * FROM users WHERE id = $1',
[req.id]
)
return result.rows[0]
}
)
Encore runs migrations on encore run and encore deploy. No separate migration tools needed.
PubSub for Message Queues
Declare message queues as first-class primitives:
import { pubsub } from 'encore.dev/pubsub'
interface UserCreatedEvent {
userId: number
email: string
}
export const userCreated = pubsub.newTopic('user-created', {
deliveryGuarantee: 'at-least-once',
})
export const createUser = api(
{ method: 'POST', path: '/users' },
async (req: { name: string; email: string }) => {
// Create user...
const userId = 1
const email = req.email
// Publish event
await userCreated.publish({
userId,
email,
})
return { userId, email }
}
)
// Subscriber
export const onUserCreated = userCreated.subscribe(
'send-welcome-email',
async (msg: UserCreatedEvent) => {
console.log(`Sending welcome email to ${msg.email}`)
// Send email via Mailgun, SendGrid, etc.
}
)
Subscribers are automatically scaled. Encore handles delivery and retries.
Cron Jobs
Schedule tasks with simple decorators:
import { cron } from 'encore.dev/cron'
export const dailyDigest = cron.newJob('daily-digest', {
title: 'Send daily digest',
every: '24h',
retryPolicy: {
maxAttempts: 3,
backoffFactor: 2,
},
})
export const handler = dailyDigest(async () => {
console.log('Sending daily digest...')
// Fetch users, aggregate data, send emails
})
Cron schedules are declared in code. No separate cron orchestration needed.
Secrets Management
Store secrets without .env files:
import { meta } from 'encore.dev'
export const sendEmail = api(
{ method: 'POST', path: '/send-email' },
async (req: { to: string; subject: string; body: string }) => {
const apiKey = meta.environment.MAILGUN_API_KEY
// Use apiKey to call Mailgun...
return { success: true }
}
)
Secrets are stored in Encore''s vault. Access them via meta.environment. No env files in version control.
Service-to-Service Calls with Type Safety
Call other Encore services with full type safety:
// users/users.ts
export const getUser = api(
{ method: 'GET', path: '/users/:id' },
async (req: { id: number }): Promise<User> => {
// ...
}
)
// posts/posts.ts
import * as users from '../users'
export const getUserPosts = api(
{ method: 'GET', path: '/posts/user/:userId' },
async (req: { userId: number }) => {
// Call users service with full type inference
const user = await users.getUser({ id: req.userId })
const posts = await db.query(
'SELECT * FROM posts WHERE user_id = $1',
[user.id]
)
return posts
}
)
Encore generates service definitions automatically. No HTTP URLs, no manual type definitions.
Local Development with encore run
Test locally before deploying:
encore run
# Output:
# Listening on http://localhost:4000
# Dashboard at http://localhost:4000/encore/dashboard
The dashboard shows:
- Request/response tracing
- Database queries
- Service dependencies
- Performance metrics
Test with curl:
curl -X POST http://localhost:4000/users \
-H 'Content-Type: application/json' \
-d '{"name":"Alice","email":"alice@example.com"}'
Deploying to AWS, GCP, Azure
Encore generates infrastructure:
# Connect to cloud provider
encore auth login
# Deploy to production
encore deploy production
# Output:
# Deploying to AWS...
# ✓ API Gateway configured
# ✓ RDS PostgreSQL provisioned
# ✓ Lambda functions deployed
# ✓ SQS queues created
# ✓ CloudWatch alarms configured
Encore provisions:
- AWS: API Gateway, Lambda, RDS, SQS, CloudWatch
- GCP: Cloud Run, Cloud SQL, Pub/Sub, Cloud Trace
- Azure: App Service, Azure SQL, Service Bus
No Terraform. No CloudFormation. Encore handles it.
Tracing and Metrics
Built-in observability:
import { telemetry } from 'encore.dev/telemetry'
export const createUser = api(
{ method: 'POST', path: '/users' },
async (req: { name: string; email: string }) => {
telemetry.recordMetric('users.created', 1)
const user = {
id: Math.random(),
...req,
}
telemetry.recordSpan('user.validation', () => {
// Validation logic
})
return user
}
)
Traces are exported to Datadog, Honeycomb, or Otel collectors automatically.
Environment Configuration
Manage different environments (dev, staging, prod):
// encore.app.ts
export default new AppConfig({
name: 'my-api',
environments: {
dev: {
location: 'us-west-1',
replicas: 1,
},
staging: {
location: 'us-west-1',
replicas: 2,
},
prod: {
location: 'us-west-1',
replicas: 3,
autoScaling: {
minReplicas: 3,
maxReplicas: 10,
targetCPUUtilization: 70,
},
},
},
})
encore deploy uses the environment-specific config.
When Encore Makes Sense
Use Encore if:
- You''re building REST APIs
- Your infrastructure is standard (PostgreSQL, APIs, queues)
- You want to avoid Terraform/CloudFormation
- Your team is TypeScript-focused
Avoid Encore if:
- You have exotic infrastructure (Cassandra, Kubernetes)
- You need fine-grained infrastructure control
- You have existing Terraform code
- Your organization requires multi-cloud flexibility
Encore is opinionated. It trades flexibility for simplicity.
Encore vs Serverless Framework
| Aspect | Encore | Serverless Framework |
|---|---|---|
| Language | TypeScript only | Any language (via plugins) |
| Infrastructure | Declarative | Mostly declarative |
| Migration | Easy from Express | Difficult from Express |
| Cloud providers | 3 (AWS, GCP, Azure) | 6+ (AWS, GCP, Azure, Heroku, etc.) |
| DX | Excellent | Good |
| Maturity | Growing | Mature |
Encore is newer but has superior developer experience. Serverless Framework is more flexible.
Checklist
- Install Encore CLI
- Create project with
encore create - Define API endpoints with
@api - Set up PostgreSQL database with
@encore.db - Add PubSub topics and subscribers
- Schedule cron jobs
- Test locally with
encore run - Set up secrets in Encore''s vault
- Configure environments
- Deploy to production with
encore deploy - Monitor tracing and metrics
Conclusion
Encore.ts bridges the gap between application code and infrastructure. By declaring infrastructure in TypeScript, you eliminate context-switching to YAML or HCL. Migrations, scaling, and deployments are one command. It won''t replace Terraform for complex multi-cloud setups, but for typical REST APIs, Encore is faster, simpler, and more maintainable. For new projects, it should be your default.