Your whole backend as a single file declaration

Database, auth, CRUD, storage, jobs, email, and realtime are keys on one object, and bun run dev starts all of it.

bun add bunderstack
Read the docs

One object is the backend

Every facility is a key, not a service to stand up. Read the file top to bottom and you know the system.

import { bunderstack } from 'bunderstack'
import { bunSql } from 'bunderstack/bun-sql'
import { pgTable, text } from 'drizzle-orm/pg-core'
import * as v from 'valibot'

const posts = pgTable('posts', {
  id: text('id').primaryKey(),
  title: text('title').notNull(),
  userId: text('userId').notNull(),
})

export const backend = bunderstack({
  schema: { posts },
  database: { adapter: bunSql() },
  auth: { secret: process.env.AUTH_SECRET! },
  access: { posts: { ownerColumn: 'userId' } },
  env: { client: { PUBLIC_APP_NAME: v.optional(v.string(), 'Example') } },
  storage: { local: true, buckets: { images: { transforms: true } } },
  email: { from: '[email protected]' },
  realtime: true,
  jobs: (j) =>
    j.define({
      digest: j.cron({
        schedule: '0 9 * * *',
        handler: async (_run, 
ctx: JobContext<{
    posts: PgTableWithColumns<{
        name: "posts";
        schema: undefined;
        columns: {
            id: PgColumn<{
                name: "id";
                tableName: "posts";
                dataType: "string";
                columnType: "PgText";
                data: string;
                driverParam: string;
                notNull: true;
                hasDefault: false;
                isPrimaryKey: true;
                isAutoincrement: false;
                hasRuntimeDefault: false;
                enumValues: [string, ...string[]];
                baseColumn: never;
                identity: undefined;
                generated: undefined;
            }, {}, {}>;
            title: PgColumn<{
                name: "title";
                tableName: "posts";
                dataType: "string";
                columnType: "PgText";
                data: string;
                driverParam: string;
                notNull: true;
                hasDefault: false;
                isPrimaryKey: false;
                isAutoincrement: false;
                hasRuntimeDefault: false;
                enumValues: [...];
                baseColumn: never;
                identity: undefined;
                generated: undefined;
            }, {}, {}>;
            userId: PgColumn<...>;
        };
        dialect: "pg";
    }>;
}, BaseEnv & {
    ...;
}>
ctx
) => {
await
ctx: JobContext<{
    posts: PgTableWithColumns<{
        name: "posts";
        schema: undefined;
        columns: {
            id: PgColumn<{
                name: "id";
                tableName: "posts";
                dataType: "string";
                columnType: "PgText";
                data: string;
                driverParam: string;
                notNull: true;
                hasDefault: false;
                isPrimaryKey: true;
                isAutoincrement: false;
                hasRuntimeDefault: false;
                enumValues: [string, ...string[]];
                baseColumn: never;
                identity: undefined;
                generated: undefined;
            }, {}, {}>;
            title: PgColumn<{
                name: "title";
                tableName: "posts";
                dataType: "string";
                columnType: "PgText";
                data: string;
                driverParam: string;
                notNull: true;
                hasDefault: false;
                isPrimaryKey: false;
                isAutoincrement: false;
                hasRuntimeDefault: false;
                enumValues: [...];
                baseColumn: never;
                identity: undefined;
                generated: undefined;
            }, {}, {}>;
            userId: PgColumn<...>;
        };
        dialect: "pg";
    }>;
}, BaseEnv & {
    ...;
}>
ctx
.email.send({
to: '[email protected]', subject:
ctx: JobContext<{
    posts: PgTableWithColumns<{
        name: "posts";
        schema: undefined;
        columns: {
            id: PgColumn<{
                name: "id";
                tableName: "posts";
                dataType: "string";
                columnType: "PgText";
                data: string;
                driverParam: string;
                notNull: true;
                hasDefault: false;
                isPrimaryKey: true;
                isAutoincrement: false;
                hasRuntimeDefault: false;
                enumValues: [string, ...string[]];
                baseColumn: never;
                identity: undefined;
                generated: undefined;
            }, {}, {}>;
            title: PgColumn<{
                name: "title";
                tableName: "posts";
                dataType: "string";
                columnType: "PgText";
                data: string;
                driverParam: string;
                notNull: true;
                hasDefault: false;
                isPrimaryKey: false;
                isAutoincrement: false;
                hasRuntimeDefault: false;
                enumValues: [...];
                baseColumn: never;
                identity: undefined;
                generated: undefined;
            }, {}, {}>;
            userId: PgColumn<...>;
        };
        dialect: "pg";
    }>;
}, BaseEnv & {
    ...;
}>
ctx
.env.PUBLIC_APP_NAME,
text: 'Daily digest', }) }, }), }), api: (o) => ({ stats: o.protected.handler(async ({
context: Omit<ApiContext<{
    posts: PgTableWithColumns<{
        name: "posts";
        schema: undefined;
        columns: {
            id: PgColumn<{
                name: "id";
                tableName: "posts";
                dataType: "string";
                columnType: "PgText";
                data: string;
                driverParam: string;
                notNull: true;
                hasDefault: false;
                isPrimaryKey: true;
                isAutoincrement: false;
                hasRuntimeDefault: false;
                enumValues: [string, ...string[]];
                baseColumn: never;
                identity: undefined;
                generated: undefined;
            }, {}, {}>;
            title: PgColumn<{
                name: "title";
                tableName: "posts";
                dataType: "string";
                columnType: "PgText";
                data: string;
                driverParam: string;
                notNull: true;
                hasDefault: false;
                isPrimaryKey: false;
                isAutoincrement: false;
                hasRuntimeDefault: false;
                enumValues: [...];
                baseColumn: never;
                identity: undefined;
                generated: undefined;
            }, {}, {}>;
            userId: PgColumn<...>;
        };
        dialect: "pg";
    }>;
}, BaseEnv & {
    ...;
}> & object, "user" | "session"> & Omit<...> & {
    ...;
}
context
}) => ({
total: 12, requestedBy:
context: Omit<ApiContext<{
    posts: PgTableWithColumns<{
        name: "posts";
        schema: undefined;
        columns: {
            id: PgColumn<{
                name: "id";
                tableName: "posts";
                dataType: "string";
                columnType: "PgText";
                data: string;
                driverParam: string;
                notNull: true;
                hasDefault: false;
                isPrimaryKey: true;
                isAutoincrement: false;
                hasRuntimeDefault: false;
                enumValues: [string, ...string[]];
                baseColumn: never;
                identity: undefined;
                generated: undefined;
            }, {}, {}>;
            title: PgColumn<{
                name: "title";
                tableName: "posts";
                dataType: "string";
                columnType: "PgText";
                data: string;
                driverParam: string;
                notNull: true;
                hasDefault: false;
                isPrimaryKey: false;
                isAutoincrement: false;
                hasRuntimeDefault: false;
                enumValues: [...];
                baseColumn: never;
                identity: undefined;
                generated: undefined;
            }, {}, {}>;
            userId: PgColumn<...>;
        };
        dialect: "pg";
    }>;
}, BaseEnv & {
    ...;
}> & object, "user" | "session"> & Omit<...> & {
    ...;
}
context
.user.id,
})), }), }) export type App = Awaited<ReturnType<typeof backend.start>>
src/bunderstack.ts

Types reach the client

Import type App and stop. Tables, procedures, inputs, outputs, and errors are inferred from the declaration.

import { QueryClient } from '@tanstack/react-query'
import { createClient } from 'bunderstack/query'
import { createAuthClient } from 'better-auth/react'
import type { App } from './bunderstack'

export const queryClient = new QueryClient()
export const api = createClient<App>({ queryClient })
export const authClient = createAuthClient()

const result = await api.stats.call()
const result: {
    total: number;
    requestedBy: string;
}
src/api-client.ts
  • Rename a column and the call sites fail, at compile time.
  • No codegen step, and no second copy of the contract.
  • One error contract from the handler to the query cache.

Use it on frontend

Queries, mutations, realtime subscriptions, and storage helpers in your React components with full inference.

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { syncRealtime } from 'bunderstack/query'
import { useEffect, useState } from 'react'
import { api, authClient } from './api-client'

export function Feed() {
  const queryClient = useQueryClient()
  const { data: 
const session: {
    user: StripEmptyObjects<{
        id: string;
        createdAt: Date;
        updatedAt: Date;
        email: string;
        emailVerified: boolean;
        name: string;
        image?: string | null | undefined;
    }>;
    session: StripEmptyObjects<{
        id: string;
        createdAt: Date;
        updatedAt: Date;
        userId: string;
        expiresAt: Date;
        token: string;
        ipAddress?: string | null | undefined;
        userAgent?: string | null | undefined;
    }>;
} | null
session
} = authClient.useSession()
const [title, setTitle] = useState('') const [file, setFile] = useState<File | null>(null) const { data:
const posts: {
    items: {
        id: string;
        title: string;
        userId: string;
    }[];
    nextCursor?: string | undefined;
    hasMore: boolean;
    total?: number | undefined;
    limit?: number | undefined;
    offset?: number | undefined;
    cursor?: string | undefined;
    q?: string | undefined;
    sort?: string | undefined;
    order?: string | undefined;
} | undefined
The last successfully resolved data for the query.
posts
} = useQuery(api.
posts: {
    create: Public<ProcedureUtils<object, Partial<{
        id: string;
        title: string;
        userId: string;
    }>, {
        id: string;
        title: string;
        userId: string;
    }, Error | ORPCErrorFromErrorMap<{
        readonly BAD_REQUEST: {
            readonly data: OptionalSchema<StrictObjectSchema<{
                readonly code: OptionalSchema<LiteralSchema<"BAD_REQUEST", undefined>, "BAD_REQUEST">;
                readonly details: OptionalSchema<UnknownSchema, undefined>;
            }, undefined>, {
                readonly code: "BAD_REQUEST";
            }>;
        };
        readonly UNAUTHORIZED: {
            ...;
        };
        ... 4 more ...;
        readonly TOO_MANY_REQUESTS: {
            ...;
        };
    }>>>;
    ... 4 more ...;
    live: Public<...>;
} & Public<...>
posts
.list.queryOptions())
const { data:
const stats: {
    total: number;
    requestedBy: string;
} | undefined
The last successfully resolved data for the query.
stats
} = useQuery(api.
stats: Public<ProcedureUtils<object, void, {
    total: number;
    requestedBy: string;
}, Error | ORPCErrorFromErrorMap<{
    readonly BAD_REQUEST: {
        readonly data: OptionalSchema<StrictObjectSchema<{
            readonly code: OptionalSchema<LiteralSchema<"BAD_REQUEST", undefined>, "BAD_REQUEST">;
            readonly details: OptionalSchema<UnknownSchema, undefined>;
        }, undefined>, {
            readonly code: "BAD_REQUEST";
        }>;
    };
    readonly UNAUTHORIZED: {
        ...;
    };
    ... 4 more ...;
    readonly TOO_MANY_REQUESTS: {
        ...;
    };
}>>>
stats
.queryOptions())
const
const createPost: UseMutationResult<{
    id: string;
    title: string;
    userId: string;
}, Error | ORPCError<"BAD_REQUEST", {
    code: "BAD_REQUEST";
    details?: unknown;
}> | ORPCError<"UNAUTHORIZED", {
    code: "UNAUTHORIZED";
    details?: unknown;
}> | ORPCError<"FORBIDDEN", {
    code: "FORBIDDEN";
    details?: unknown;
}> | ORPCError<"NOT_FOUND", {
    code: "NOT_FOUND";
    details?: unknown;
}> | ORPCError<"CONFLICT", {
    code: "CONFLICT";
    details?: unknown;
}> | ORPCError<"PAYLOAD_TOO_LARGE", {
    code: "PAYLOAD_TOO_LARGE";
    details?: unknown;
}> | ORPCError<"TOO_MANY_REQUESTS", {
    code: "TOO_MANY_REQUESTS";
    details?: unknown;
}>, Partial<...>, unknown>
createPost
= useMutation(api.
posts: {
    create: Public<ProcedureUtils<object, Partial<{
        id: string;
        title: string;
        userId: string;
    }>, {
        id: string;
        title: string;
        userId: string;
    }, Error | ORPCErrorFromErrorMap<{
        readonly BAD_REQUEST: {
            readonly data: OptionalSchema<StrictObjectSchema<{
                readonly code: OptionalSchema<LiteralSchema<"BAD_REQUEST", undefined>, "BAD_REQUEST">;
                readonly details: OptionalSchema<UnknownSchema, undefined>;
            }, undefined>, {
                readonly code: "BAD_REQUEST";
            }>;
        };
        readonly UNAUTHORIZED: {
            ...;
        };
        ... 4 more ...;
        readonly TOO_MANY_REQUESTS: {
            ...;
        };
    }>>>;
    ... 4 more ...;
    live: Public<...>;
} & Public<...>
posts
.create.mutationOptions())
useEffect(() => { const live = syncRealtime({ api, queryClient, tables: ['posts'] }) return () => live.close() }, [queryClient]) const onSubmit = async (e: React.FormEvent) => { e.preventDefault() if (file) await api.files.images.upload(file)
const createPost: UseMutationResult<{
    id: string;
    title: string;
    userId: string;
}, Error | ORPCError<"BAD_REQUEST", {
    code: "BAD_REQUEST";
    details?: unknown;
}> | ORPCError<"UNAUTHORIZED", {
    code: "UNAUTHORIZED";
    details?: unknown;
}> | ORPCError<"FORBIDDEN", {
    code: "FORBIDDEN";
    details?: unknown;
}> | ORPCError<"NOT_FOUND", {
    code: "NOT_FOUND";
    details?: unknown;
}> | ORPCError<"CONFLICT", {
    code: "CONFLICT";
    details?: unknown;
}> | ORPCError<"PAYLOAD_TOO_LARGE", {
    code: "PAYLOAD_TOO_LARGE";
    details?: unknown;
}> | ORPCError<"TOO_MANY_REQUESTS", {
    code: "TOO_MANY_REQUESTS";
    details?: unknown;
}>, Partial<...>, unknown>
createPost
.mutate({ title })
setTitle('') } return ( <div> <header> <span>Signed in as {
const session: {
    user: StripEmptyObjects<{
        id: string;
        createdAt: Date;
        updatedAt: Date;
        email: string;
        emailVerified: boolean;
        name: string;
        image?: string | null | undefined;
    }>;
    session: StripEmptyObjects<{
        id: string;
        createdAt: Date;
        updatedAt: Date;
        userId: string;
        expiresAt: Date;
        token: string;
        ipAddress?: string | null | undefined;
        userAgent?: string | null | undefined;
    }>;
} | null
session
?.user.name}</span>
<h2>Posts ({
const stats: {
    total: number;
    requestedBy: string;
} | undefined
The last successfully resolved data for the query.
stats
?.total ?? 0})</h2>
</header> <form onSubmit={onSubmit}> <input value={title} onChange={(e) => setTitle(e.target.value)} placeholder="New post" /> <input type="file" accept="image/*" onChange={(e) => setFile(e.target.files?.[0] ?? null)} /> <button type="submit">Publish</button> </form> <ul> {
const posts: {
    items: {
        id: string;
        title: string;
        userId: string;
    }[];
    nextCursor?: string | undefined;
    hasMore: boolean;
    total?: number | undefined;
    limit?: number | undefined;
    offset?: number | undefined;
    cursor?: string | undefined;
    q?: string | undefined;
    sort?: string | undefined;
    order?: string | undefined;
} | undefined
The last successfully resolved data for the query.
posts
?.items.map((
post: {
    id: string;
    title: string;
    userId: string;
}
post
) => (
items: {
    id: string;
    title: string;
    userId: string;
}[]
<li key={
post: {
    id: string;
    title: string;
    userId: string;
}
post
.id}>
<span>{
post: {
    id: string;
    title: string;
    userId: string;
}
post
.title}</span>
<img src={api.files.images.url(
post: {
    id: string;
    title: string;
    userId: string;
}
post
.id, { w: 32, h: 32 })} />
</li> ))} </ul> </div> ) }
src/Feed.tsx
  • Auto-generated query and mutation options for TanStack Query.
  • authClient.useSession() for reactive user session state.
  • syncRealtime automatically patches the query cache on live changes.
  • Direct file uploads and on-the-fly transformed image URLs.

Works out-of-the-box

Each of these is useful alone. Together they remove the adapters and lifecycle code that normally fill the space between them.

Database & Schema

Drizzle ORM · Bun.sql / Postgres
schema: { posts },
database: { adapter: bunSql() }

Authentication

Better Auth
auth: {
  secret: process.env.AUTH_SECRET!
}

Access & CRUD

Drizzle ORM · oRPC
access: {
  posts: { ownerColumn: 'userId' }
}

Typed API & Procedures

oRPC · Standard Schema
api: (o) => ({
  stats: o.protected.handler(async ({ context }) => ({
    total: 12,
    requestedBy: context.user.id,
  })),
})

S3 Storage & Transforms

Bun.s3 / S3 · Bun.Image
storage: {
  local: true,
  buckets: { images: { transforms: true } }
}

Transactional Email

Nodemailer · Resend / SMTP
email: {
  from: '[email protected]'
}

Realtime Sync

SSE · Redis · WebSockets
realtime: true

Background Jobs & Cron

Durable Scheduler · Queue
jobs: (j) =>
  j.define({
    digest: j.cron({
      schedule: '0 9 * * *',
      handler: async (_run, ctx) => {
        await ctx.email.send({ ... })
      },
    }),
  })

Examples

Application-sized tests of the mental model, not isolated feature demos.

Declare it once. Run it with one command.