Encore vs Nitric vs Shuttle: Cloud-Native Backend Frameworks Compared (2026)
TL;DR: Encore is the TypeScript/Go backend framework that infers infrastructure from your code — define API endpoints, databases, and pub/sub with type-safe primitives, and Encore provisions everything automatically. Nitric is the cloud-agnostic framework — declare what cloud resources you need (APIs, queues, storage, schedules), write handlers in any language, and deploy to AWS/GCP/Azure without lock-in. Shuttle is the Rust backend framework with built-in infrastructure — annotate your Rust functions with macros, and Shuttle provisions databases, secrets, and static assets on deploy. In 2026: Encore for TypeScript/Go with automatic infrastructure, Nitric for polyglot cloud-agnostic backends, Shuttle for Rust backends with zero DevOps.
Key Takeaways
- Encore: TypeScript + Go. Infrastructure-from-code — Encore analyzes your code and provisions databases, pub/sub, caches, cron jobs. Local dev dashboard, distributed tracing. Best for teams wanting zero-config infrastructure with strong typing
- Nitric: Any language (TS, Python, Go, Java, .NET, Dart). Declare resources → write handlers → deploy anywhere. Pulumi-based provisioning, cloud-agnostic. Best for teams wanting cloud portability and language flexibility
- Shuttle: Rust-only. Macro-based infrastructure —
#[shuttle_runtime::main]with resource annotations. Built-in Postgres, secrets, static files. Best for Rust developers wanting the simplest cloud deployment
Encore — Infrastructure from Code
Encore analyzes your TypeScript or Go code and automatically provisions the infrastructure — APIs, databases, pub/sub, caches, and cron jobs.
API Endpoints
// encore.app — project configuration
// { "id": "my-app" }
// backend/users/users.ts — API service
import { api } from "encore.dev/api";
import { SQLDatabase } from "encore.dev/storage/sqldb";
// Define the database — Encore provisions it automatically
const db = new SQLDatabase("users", {
migrations: "./migrations",
});
interface User {
id: number;
name: string;
email: string;
createdAt: Date;
}
interface CreateUserParams {
name: string;
email: string;
}
// Public API endpoint — Encore generates routes, docs, and client
export const create = api(
{ method: "POST", path: "/users", expose: true },
async (params: CreateUserParams): Promise<User> => {
const row = await db.queryRow`
INSERT INTO users (name, email, created_at)
VALUES (${params.name}, ${params.email}, NOW())
RETURNING id, name, email, created_at
`;
return {
id: row.id,
name: row.name,
email: row.email,
createdAt: row.created_at,
};
}
);
export const get = api(
{ method: "GET", path: "/users/:id", expose: true },
async ({ id }: { id: number }): Promise<User> => {
return db.queryRow`
SELECT id, name, email, created_at FROM users WHERE id = ${id}
`;
}
);
// Auth middleware — Encore handles auth at the framework level
export const list = api(
{ method: "GET", path: "/users", expose: true, auth: true },
async (): Promise<{ users: User[] }> => {
const rows = await db.query`SELECT * FROM users ORDER BY created_at DESC`;
return { users: rows };
}
);
Pub/Sub and Background Jobs
// backend/notifications/notifications.ts
import { Topic, Subscription } from "encore.dev/pubsub";
import { CronJob } from "encore.dev/cron";
// Define a topic — Encore provisions the message queue
interface UserCreatedEvent {
userId: number;
email: string;
name: string;
}
export const userCreated = new Topic<UserCreatedEvent>("user-created", {
deliveryGuarantee: "at-least-once",
});
// Subscribe to events — Encore wires up the subscription
const _ = new Subscription(userCreated, "send-welcome-email", {
handler: async (event: UserCreatedEvent) => {
await sendWelcomeEmail(event.email, event.name);
console.log(`Welcome email sent to ${event.email}`);
},
retryPolicy: { maxRetries: 3 },
});
// Publish from another service
// In users service:
// await userCreated.publish({ userId: user.id, email: user.email, name: user.name });
// Cron job — Encore schedules it automatically
const dailyDigest = new CronJob("daily-digest", {
title: "Send daily digest emails",
schedule: "0 9 * * *", // 9 AM daily
endpoint: sendDailyDigest,
});
export const sendDailyDigest = api(
{ method: "POST", path: "/internal/daily-digest" },
async () => {
const users = await getActiveUsers();
for (const user of users) {
await sendDigestEmail(user);
}
}
);
Caching and Secrets
// backend/products/products.ts
import { CacheCluster, CacheKeyspace } from "encore.dev/storage/cache";
import { secret } from "encore.dev/config";
// Cache — Encore provisions Redis automatically
const cluster = new CacheCluster("products-cache");
const productCache = new CacheKeyspace<Product>(cluster, {
keyPattern: "product/:id",
defaultExpiry: { hours: 1 },
});
export const getProduct = api(
{ method: "GET", path: "/products/:id", expose: true },
async ({ id }: { id: string }): Promise<Product> => {
// Try cache first
const cached = await productCache.get(id);
if (cached) return cached;
// Fetch from DB
const product = await db.queryRow`SELECT * FROM products WHERE id = ${id}`;
await productCache.set(id, product);
return product;
}
);
// Secrets — Encore manages them securely
const stripeKey = secret("StripeSecretKey");
// Set via: encore secret set --type prod StripeSecretKey
Local Development
# Install Encore CLI
curl -L https://encore.dev/install.sh | bash
# Create a new project
encore app create my-app --example=ts/hello-world
# Run locally — Encore starts all services, databases, caches
encore run
# Local development dashboard — auto-generated API docs,
# distributed tracing, database explorer
# Available at http://localhost:9400
# Deploy to Encore Cloud
encore deploy --env=staging
Nitric — Cloud-Agnostic Backend Framework
Nitric lets you declare cloud resources and write handlers in any language — then deploy to AWS, GCP, or Azure without lock-in.
API and Resources
// services/users.ts
import { api, topic, kv, bucket, schedule } from "@nitric/sdk";
// Declare resources — Nitric provisions them on deploy
const usersTopic = topic("user-events").allow("publish");
const usersKv = kv("users").allow("get", "set", "delete");
const avatarsBucket = bucket("avatars").allow("read", "write");
// API endpoints
const usersApi = api("users");
usersApi.post("/users", async (ctx) => {
const { name, email } = ctx.req.json();
const userId = crypto.randomUUID();
await usersKv.set(userId, { id: userId, name, email, createdAt: new Date() });
// Publish event
await usersTopic.publish({
type: "user.created",
userId,
email,
name,
});
ctx.res.json({ id: userId, name, email });
});
usersApi.get("/users/:id", async (ctx) => {
const { id } = ctx.req.params;
const user = await usersKv.get(id);
if (!user) {
ctx.res.status = 404;
ctx.res.json({ error: "User not found" });
return;
}
ctx.res.json(user);
});
// File upload
usersApi.post("/users/:id/avatar", async (ctx) => {
const { id } = ctx.req.params;
const body = ctx.req.data;
await avatarsBucket.file(`${id}/avatar.png`).write(body);
ctx.res.json({ message: "Avatar uploaded" });
});
Event Handling and Schedules
// services/notifications.ts
import { topic, schedule } from "@nitric/sdk";
// Subscribe to events
const userEvents = topic("user-events").allow("subscribe");
userEvents.subscribe(async (ctx) => {
const event = ctx.req.json();
switch (event.type) {
case "user.created":
await sendWelcomeEmail(event.email, event.name);
break;
case "user.deleted":
await cleanupUserData(event.userId);
break;
}
});
// Scheduled tasks
schedule("daily-digest").every("1 day", async (ctx) => {
const users = await getActiveUsers();
for (const user of users) {
await sendDigestEmail(user);
}
});
schedule("cleanup").cron("0 2 * * *", async (ctx) => {
// Run at 2 AM daily
await cleanupExpiredSessions();
});
Middleware and Auth
// services/middleware.ts
import { api, oidcRule } from "@nitric/sdk";
// OIDC authentication
const secureApi = api("secure", {
security: {
oidc: oidcRule({
name: "auth0",
issuer: "https://acme.auth0.com/",
audiences: ["https://api.acme.com"],
}),
},
});
secureApi.get("/profile", async (ctx) => {
// ctx.req.context contains the verified JWT claims
const userId = ctx.req.context.identity.sub;
const profile = await getProfile(userId);
ctx.res.json(profile);
});
// Custom middleware
const loggingApi = api("logged", {
middleware: [
async (ctx, next) => {
const start = Date.now();
await next(ctx);
console.log(`${ctx.req.method} ${ctx.req.path} - ${Date.now() - start}ms`);
},
async (ctx, next) => {
ctx.res.headers["X-Request-ID"] = crypto.randomUUID();
await next(ctx);
},
],
});
Deployment Configuration
# nitric.yaml — project configuration
name: my-app
services:
- match: services/*.ts
start: npx ts-node $SERVICE_PATH
# nitric-aws.yaml — AWS provider configuration
provider: nitric/aws@latest
region: us-east-1
config:
lambda:
memory: 512
timeout: 30
api:
domains:
users: api.acme.com
# Deploy
# nitric up -s nitric-aws.yaml
# nitric-gcp.yaml — same app, different cloud
# provider: nitric/gcp@latest
# region: us-central1
Shuttle — Rust Backend Framework
Shuttle gives Rust developers the simplest deployment experience — annotate functions with macros and Shuttle provisions everything.
API with Axum
// src/main.rs
use axum::{
extract::{Path, State},
routing::{get, post},
Json, Router,
};
use serde::{Deserialize, Serialize};
use shuttle_axum::ShuttleAxum;
use shuttle_shared_db::Postgres;
use sqlx::PgPool;
#[derive(Serialize, Deserialize)]
struct User {
id: i32,
name: String,
email: String,
}
#[derive(Deserialize)]
struct CreateUser {
name: String,
email: String,
}
async fn create_user(
State(pool): State<PgPool>,
Json(params): Json<CreateUser>,
) -> Json<User> {
let user = sqlx::query_as!(
User,
"INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email",
params.name,
params.email
)
.fetch_one(&pool)
.await
.unwrap();
Json(user)
}
async fn get_user(
State(pool): State<PgPool>,
Path(id): Path<i32>,
) -> Json<User> {
let user = sqlx::query_as!(
User,
"SELECT id, name, email FROM users WHERE id = $1",
id
)
.fetch_one(&pool)
.await
.unwrap();
Json(user)
}
async fn list_users(State(pool): State<PgPool>) -> Json<Vec<User>> {
let users = sqlx::query_as!(User, "SELECT id, name, email FROM users")
.fetch_all(&pool)
.await
.unwrap();
Json(users)
}
// Shuttle macro provisions Postgres automatically
#[shuttle_runtime::main]
async fn main(
#[shuttle_shared_db::Postgres] pool: PgPool,
) -> ShuttleAxum {
// Run migrations
sqlx::migrate!("./migrations")
.run(&pool)
.await
.unwrap();
let router = Router::new()
.route("/users", post(create_user).get(list_users))
.route("/users/:id", get(get_user))
.with_state(pool);
Ok(router.into())
}
Secrets and Static Assets
use shuttle_runtime::SecretStore;
use shuttle_static_folder::StaticFolder;
use std::path::PathBuf;
#[shuttle_runtime::main]
async fn main(
#[shuttle_shared_db::Postgres] pool: PgPool,
#[shuttle_runtime::Secrets] secrets: SecretStore,
#[shuttle_static_folder::StaticFolder] static_folder: PathBuf,
) -> ShuttleAxum {
// Access secrets (set via `shuttle secrets set`)
let stripe_key = secrets.get("STRIPE_SECRET_KEY").unwrap();
let jwt_secret = secrets.get("JWT_SECRET").unwrap();
let router = Router::new()
.route("/api/users", post(create_user))
.nest_service("/static", ServeDir::new(static_folder))
.with_state(AppState { pool, stripe_key, jwt_secret });
Ok(router.into())
}
Deployment
# Install Shuttle CLI
cargo install cargo-shuttle
# Initialize a project
cargo shuttle init --name my-app --template axum
# Run locally (provisions local Postgres)
cargo shuttle run
# Deploy to Shuttle Cloud
cargo shuttle deploy
# Set secrets
cargo shuttle secrets set STRIPE_SECRET_KEY sk_live_...
# View logs
cargo shuttle logs
# View deployment status
cargo shuttle status
Feature Comparison
| Feature | Encore | Nitric | Shuttle |
|---|---|---|---|
| Languages | TypeScript, Go | TS, Python, Go, Java, .NET, Dart | Rust |
| Approach | Infrastructure-from-code | Resource declaration | Macro annotations |
| Cloud Providers | Encore Cloud, AWS, GCP | AWS, GCP, Azure | Shuttle Cloud |
| Database | ✅ (auto-provisioned Postgres) | KV store, SQL (via providers) | ✅ (Postgres, Turso) |
| Pub/Sub | ✅ (Topic/Subscription) | ✅ (topic/subscribe) | ❌ (external) |
| Caching | ✅ (auto-provisioned Redis) | ❌ (external) | ❌ (external) |
| Object Storage | ✅ (Buckets) | ✅ (bucket API) | ✅ (static folder) |
| Cron/Schedules | ✅ (CronJob) | ✅ (schedule) | ✅ (shuttle-cron) |
| Secrets | ✅ (managed) | ✅ (environment) | ✅ (SecretStore) |
| Auth | ✅ (built-in) | ✅ (OIDC rules) | DIY |
| API Docs | ✅ (auto-generated) | ❌ | ❌ |
| Tracing | ✅ (distributed tracing) | ❌ (external) | ❌ (external) |
| Local Dev | ✅ (full local stack) | ✅ (nitric start) | ✅ (cargo shuttle run) |
| Dev Dashboard | ✅ (rich UI) | Basic CLI | Basic CLI |
| Cloud Agnostic | Encore Cloud + AWS/GCP | ✅ (AWS/GCP/Azure) | Shuttle Cloud only |
| Self-Host | ✅ (generate Terraform) | ✅ (Pulumi-based) | ❌ |
| License | BSL → Apache 2.0 | Apache 2.0 | Apache 2.0 |
| Maturity | Growing | Growing | Growing |
| Best For | TS/Go auto-infra | Polyglot, cloud-portable | Rust simplicity |
When to Use Each
Choose Encore if:
- You want infrastructure automatically provisioned from your code (no YAML)
- TypeScript or Go is your backend language
- Auto-generated API documentation and distributed tracing are valuable
- A rich local development dashboard accelerates your workflow
- You want to deploy to Encore Cloud or self-host to AWS/GCP
Choose Nitric if:
- Cloud portability (AWS ↔ GCP ↔ Azure) without code changes is important
- Your team uses multiple languages and needs a unified resource model
- You want Pulumi-based provisioning for infrastructure customization
- Avoiding vendor lock-in is a priority
- You need APIs, queues, storage, and schedules in a single abstraction
Choose Shuttle if:
- You're building a Rust backend and want the simplest deployment path
- Zero DevOps —
cargo shuttle deployprovisions everything - Postgres and secrets management without configuration is appealing
- You're prototyping or building side projects in Rust
- The Rust ecosystem (Axum, Actix, Rocket) is your framework of choice
Methodology
Feature comparison based on Encore (TypeScript/Go), Nitric, and Shuttle documentation as of March 2026. Encore evaluated on infrastructure-from-code model and local dev experience. Nitric evaluated on cloud portability and resource abstraction. Shuttle evaluated on Rust integration and deployment simplicity. Code examples use official APIs and CLI tools.