External Databases
Every Aerostack Function comes with a built-in database (env.DB — SQLite-compatible, zero config). But you can also connect to any external database — Neon (Postgres), PlanetScale (MySQL), Turso, Supabase, MongoDB, Redis, and more.
Drizzle ORM (Recommended)
Section titled “Drizzle ORM (Recommended)”Aerostack uses Drizzle ORM as the default ORM. The CLI has built-in support for Drizzle schema generation and migrations.
Why Drizzle?
Section titled “Why Drizzle?”- Edge-native — designed to run on Cloudflare Workers (no Node.js-only dependencies)
- Type-safe — full TypeScript inference from your schema
- Lightweight — zero runtime overhead, compiles to raw SQL
- Multi-database — same API for SQLite (D1), Postgres (Neon), MySQL (PlanetScale)
Using Drizzle with env.DB (Built-in)
Section titled “Using Drizzle with env.DB (Built-in)”-
Install Drizzle
Terminal window npm install drizzle-ormnpm install -D drizzle-kit -
Define your schema
Create
src/schema.ts:import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'export const users = sqliteTable('users', {id: integer('id').primaryKey({ autoIncrement: true }),email: text('email').notNull().unique(),name: text('name').notNull(),createdAt: text('created_at').default('(datetime(\'now\'))'),})export const posts = sqliteTable('posts', {id: integer('id').primaryKey({ autoIncrement: true }),userId: integer('user_id').references(() => users.id),title: text('title').notNull(),content: text('content').notNull(),publishedAt: text('published_at'),}) -
Generate types
Terminal window aerostack generate types -
Use in your function
import { drizzle } from 'drizzle-orm/d1'import { eq } from 'drizzle-orm'import * as schema from './schema'interface Env {DB: Database}export default {async fetch(request: Request, env: Env): Promise<Response> {const db = drizzle(env.DB, { schema })// Type-safe queriesconst allUsers = await db.select().from(schema.users)const userWithPosts = await db.query.users.findFirst({where: eq(schema.users.email, 'alice@example.com'),with: { posts: true }})// Insertconst newUser = await db.insert(schema.users).values({email: 'bob@example.com',name: 'Bob'}).returning()return Response.json({ users: allUsers })}}
Neon (Serverless Postgres)
Section titled “Neon (Serverless Postgres)”Neon provides serverless Postgres with a HTTP driver that works on Cloudflare Workers.
-
Create a Neon database
Sign up at neon.tech and create a project. Copy the connection string.
-
Install dependencies
Terminal window npm install drizzle-orm @neondatabase/serverlessnpm install -D drizzle-kit -
Define your schema
Create
src/schema.ts:import { pgTable, serial, text, timestamp, integer } from 'drizzle-orm/pg-core'export const users = pgTable('users', {id: serial('id').primaryKey(),email: text('email').notNull().unique(),name: text('name').notNull(),createdAt: timestamp('created_at').defaultNow(),})export const posts = pgTable('posts', {id: serial('id').primaryKey(),userId: integer('user_id').references(() => users.id),title: text('title').notNull(),content: text('content').notNull(),}) -
Set your connection string
Terminal window # Local developmentecho "DATABASE_URL=postgres://user:pass@ep-xxx.us-east-2.aws.neon.tech/mydb" >> .dev.vars# Productionaerostack secrets set DATABASE_URL -
Use in your function
import { neon } from '@neondatabase/serverless'import { drizzle } from 'drizzle-orm/neon-http'import * as schema from './schema'interface Env {DATABASE_URL: stringDB: Database // You can still use the built-in DB alongside Neon}export default {async fetch(request: Request, env: Env): Promise<Response> {const sql = neon(env.DATABASE_URL)const db = drizzle(sql, { schema })const users = await db.select().from(schema.users)return Response.json({ users })}}
Turso (Distributed SQLite)
Section titled “Turso (Distributed SQLite)”Turso provides distributed SQLite with edge replicas — similar to env.DB but with multi-region write support.
npm install @libsql/client drizzle-ormimport { createClient } from '@libsql/client/web'import { drizzle } from 'drizzle-orm/libsql'import * as schema from './schema'
interface Env { TURSO_URL: string TURSO_AUTH_TOKEN: string}
export default { async fetch(request: Request, env: Env): Promise<Response> { const client = createClient({ url: env.TURSO_URL, authToken: env.TURSO_AUTH_TOKEN, })
const db = drizzle(client, { schema }) const users = await db.select().from(schema.users)
return Response.json({ users }) }}Supabase (Postgres + Realtime)
Section titled “Supabase (Postgres + Realtime)”npm install @supabase/supabase-jsimport { createClient } from '@supabase/supabase-js'
interface Env { SUPABASE_URL: string SUPABASE_ANON_KEY: string}
export default { async fetch(request: Request, env: Env): Promise<Response> { const supabase = createClient(env.SUPABASE_URL, env.SUPABASE_ANON_KEY)
const { data: users, error } = await supabase .from('users') .select('*') .eq('active', true)
if (error) return Response.json({ error: error.message }, { status: 500 }) return Response.json({ users }) }}MongoDB Atlas
Section titled “MongoDB Atlas”npm install mongodbimport { MongoClient } from 'mongodb'
interface Env { MONGODB_URI: string}
export default { async fetch(request: Request, env: Env): Promise<Response> { const client = new MongoClient(env.MONGODB_URI)
try { const db = client.db('myapp') const users = await db.collection('users').find({ active: true }).toArray() return Response.json({ users }) } finally { await client.close() } }}Upstash Redis
Section titled “Upstash Redis”For caching and real-time data beyond what env.CACHE provides (pub/sub, sorted sets, streams):
npm install @upstash/redisimport { Redis } from '@upstash/redis'
interface Env { UPSTASH_REDIS_REST_URL: string UPSTASH_REDIS_REST_TOKEN: string}
export default { async fetch(request: Request, env: Env): Promise<Response> { const redis = new Redis({ url: env.UPSTASH_REDIS_REST_URL, token: env.UPSTASH_REDIS_REST_TOKEN, })
// Sorted set for leaderboard await redis.zadd('leaderboard', { score: 100, member: 'alice' }) const top10 = await redis.zrange('leaderboard', 0, 9, { rev: true, withScores: true })
return Response.json({ leaderboard: top10 }) }}Best Practices
Section titled “Best Practices”Combine built-in + external
Section titled “Combine built-in + external”You don’t have to choose one or the other. Use env.DB for fast, local data and an external database for specialized needs:
export default { async fetch(request: Request, env: Env): Promise<Response> { const sql = neon(env.DATABASE_URL) const pgDb = drizzle(sql)
// Fast: session lookup from built-in DB const session = await env.DB .prepare('SELECT * FROM sessions WHERE token = ?') .bind(token) .first()
// Specialized: complex Postgres query for analytics const analytics = await pgDb.execute( sql`SELECT date_trunc('day', created_at) as day, count(*) FROM events GROUP BY 1` )
// Cache the result await env.CACHE.put('analytics:daily', JSON.stringify(analytics), { expirationTtl: 3600 })
return Response.json({ session, analytics }) }}Cache external queries
Section titled “Cache external queries”External database calls are 50-200x slower than env.DB. Always cache frequent reads:
async function getUser(env: Env, userId: string) { // Check cache first const cached = await env.CACHE.get(`user:${userId}`, 'json') if (cached) return cached
// External DB query const sql = neon(env.DATABASE_URL) const result = await sql`SELECT * FROM users WHERE id = ${userId}` const user = result[0]
// Cache for 5 minutes if (user) { await env.CACHE.put(`user:${userId}`, JSON.stringify(user), { expirationTtl: 300 }) }
return user}Set secrets properly
Section titled “Set secrets properly”Never put database credentials in aerostack.toml or source code:
# Local developmentecho "DATABASE_URL=postgres://..." >> .dev.vars
# Productionaerostack secrets set DATABASE_URLAdd .dev.vars to .gitignore.
Database Compatibility Matrix
Section titled “Database Compatibility Matrix”| Database | Package | Edge Compatible | Drizzle Support |
|---|---|---|---|
Built-in (env.DB) | None needed | Native | Yes (d1) |
| Neon (Postgres) | @neondatabase/serverless | Yes | Yes (neon-http) |
| Turso (SQLite) | @libsql/client | Yes | Yes (libsql) |
| PlanetScale (MySQL) | @planetscale/database | Yes | Yes (planetscale) |
| Supabase (Postgres) | @supabase/supabase-js | Yes | Via Neon driver |
| MongoDB Atlas | mongodb | Partial | No |
| Upstash Redis | @upstash/redis | Yes | No (not SQL) |
| CockroachDB | @neondatabase/serverless | Yes | Via pg driver |