Build the stack, not the plumbing
Drop a Coday SDK into any project and skip the auth boilerplate, the S3 IAM dance, the WebSocket reconnect logic, and the cron job container — Coday provisions the backend on your first deploy.
One install for everything
All Coday SDKs share a tiny core (@codayinc/core) for env reading, token storage, and HTTP. Install just the ones you use.
pnpm add @codayinc/auth @codayinc/storage @codayinc/realtime @codayinc/worker
Push to GitHub. Coday detects the imports in package.json and, on first deploy:
- Provisions a per-project Aurora database.
- Runs idempotent _coday_* table migrations.
- Allocates a per-project S3 prefix and Redis namespace.
- Injects COD_* env vars into your ECS task.
Auth — Coday-managed OAuth + magic link
One OAuth flow. No client_ids. No callback registration. Sign in with GitHub, Google, magic link, email + password, or TOTP — all routed through Coday's master OAuth apps and stored in your per-project database.
Client (browser)
import { signIn, signOut, getSession, consumeOauthCallback } from "@codayinc/auth"
// Call once on app boot — consumes #coday_token=... after OAuth redirect.
consumeOauthCallback()
await signIn("github") // → 302 to Coday → 302 to GitHub
await signIn("email", { email, password })
await signIn("magic-link", { email })
const session = await getSession()
await signOut()React hooks
import { useAuth, useUser } from "@codayinc/auth/react"
function Header() {
const { user, status, signOut } = useAuth()
if (status === "loading") return <Spinner />
if (!user) return <SignInButton />
return <UserMenu user={user} onSignOut={signOut} />
}Server (Next.js App Router)
import { auth, requireAuth, verifyToken } from "@codayinc/auth/server"
export async function GET() {
const session = await auth()
if (!session) return new Response("Unauthorized", { status: 401 })
return Response.json({ user: session.user })
}Storage — S3 without the IAM
Upload files from the browser through Coday's API. No CORS configuration, no presigned-URL boilerplate, no S3 SDK in the bundle. Each project gets a private prefix; metadata mirrors into your project DB so you can SELECT directly.
import { storage } from "@codayinc/storage"
// Direct upload (≤ 50 MB)
const meta = await storage.upload("avatars/me.jpg", file, { visibility: "public" })
// Larger files — presigned PUT
const { url, headers } = await storage.getSignedUrl("video.mp4", { method: "PUT", expiresInSec: 600 })
await fetch(url, { method: "PUT", body: file, headers })
const list = await storage.list({ prefix: "avatars/", limit: 50 })
await storage.delete("avatars/old.jpg")Realtime — channels + presence with auto-reconnect
WebSocket channels with exponential-backoff reconnect, presence sets, and server-side publish for cron and webhooks. Wire protocol is JSON-over-WS. Cross-instance fan-out runs over Redis pub/sub.
import { realtime } from "@codayinc/realtime"
import { useChannel, usePresence } from "@codayinc/realtime/react"
function ChatRoom({ id }: { id: string }) {
const { messages, publish } = useChannel<{ text: string }>(`room:${id}`)
const { members } = usePresence(`room:${id}`)
return (
<>
<Online members={members} />
<Log messages={messages} />
<Composer onSend={(text) => publish("say", { text })} />
</>
)
}
// Server-side (cron, webhook, etc.)
import { publishFromServer } from "@codayinc/realtime/server"
await publishFromServer(`room:${id}`, "system", { text: "Daily digest ready" })Note: the WebSocket gateway service is on the Sprint S8 rollout path. SDK is shipped today; the realtime.coday.io ALB listener follows in S8.
Worker — background queues + cron
defineWorker + queue wrap BullMQ on Coday's managed Redis (one namespace per project). On every deploy, Coday AST-scans your repo for cron schedules and provisions an EventBridge rule — no separate Vercel cron config or Kubernetes CronJob.
import { defineWorker, queue } from "@codayinc/worker"
defineWorker<{ to: string; body: string }>({
queue: "email",
concurrency: 10,
async handler({ to, body }) {
await sendMail(to, body)
},
})
// Enqueue from anywhere in your code
await queue<{ to: string; body: string }>("email").add({
to: "[email protected]",
body: "Welcome",
})
// Or define a periodic job (Coday provisions the EventBridge rule)
defineWorker({
queue: "daily-rollup",
cron: "0 6 * * *",
async handler() {
await rollup()
},
})Note: cron AST-scan is shipped today; EventBridge auto-provision lands in Sprint S8. Until then, the SDK still runs your queues; periodic schedules just need a manual rule.
Coday-injected env vars
Coday injects these into your ECS task on every deploy. Don't set them manually in local dev — point COD_*_URL to a Coday test deployment instead.
- COD_PROJECT_ID — Coday project ID
- COD_AUTH_URL, COD_AUTH_SECRET
- COD_STORAGE_URL, COD_STORAGE_CDN, COD_STORAGE_TOKEN
- COD_REALTIME_URL, COD_REALTIME_TOKEN
- COD_WORKER_REDIS_URL, COD_WORKER_TOKEN
- DATABASE_URL — per-project Postgres (also used by your own Prisma/Drizzle)
From Supabase or Vercel + Supabase
The bigger surface (auth + storage + realtime + DB + deploy) collapses into a single workspace. Migration roughly looks like:
- Replace @supabase/supabase-js auth calls with @codayinc/auth.
- Move Supabase Storage buckets → @codayinc/storage (the API surface lines up almost 1:1).
- Replace Realtime channels with @codayinc/realtime.
- Migrate your Postgres schema; Coday provisions the DB on first deploy.
- Move Vercel cron config into defineWorker({ cron: ... }).
A step-by-step migration playbook with full code mappings is in flight — see [email protected] if you want a walkthrough for your project.