[READ-ONLY] Mirror of https://github.com/okikio/backend-utils.
TypeScript 92%
PLpgSQL 7%
Shell <1%
<1%

README.md

Backend Utilities Monorepo #

High quality backend utilities and patterns for building robust APIs with TypeScript, Deno, and Node.js.


Project Architecture #

High-Level Structure #

┌───────────────────────────────────────────────────────────────┐
│                    BACKEND-UTILS MONOREPO                     │
├───────────────────────────────────────────────────────────────┤
│                                                               │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐        │
│  │   packages/  │  │  supabase/   │  │    Root      │        │
│  │   backend    │  │  functions   │  │   Scripts    │        │
│  │              │  │              │  │              │        │
│  │  Query DSL   │  │ Edge Runtime │  │  Node.js +   │        │
│  │  Response    │◀─│  (Deno 2)    │  │   pnpm       │        │
│  │  Utilities   │  │              │  │              │        │
│  └──────────────┘  └──────────────┘  └──────────────┘        │
│         ▲                  │                                  │
│         │                  ▼                                  │
│  ┌──────────────┐  ┌──────────────┐                          │
│  │   packages/  │  │  supabase/   │                          │
│  │    shared    │  │  migrations  │                          │
│  │              │  │              │                          │
│  │  Types from  │  │  PostgreSQL  │                          │
│  │  Database    │  │   Schema     │                          │
│  └──────────────┘  └──────────────┘                          │
│                                                               │
└───────────────────────────────────────────────────────────────┘

Runtime Context          Language        Package Manager
─────────────────        ────────        ───────────────
Edge Functions     →     Deno 2     →    JSR imports
Root Scripts       →     Node 22    →    pnpm workspaces
Database Types     →     Generated  →    From PostgreSQL

Mental Model: Request Flow #

HTTP Request                  Edge Function              Database
────────────                  ─────────────              ────────

┌──────────┐                 ┌──────────┐              ┌──────────┐
│  Client  │────request─────▶│  Hono    │─────query───▶│Postgres  │
│          │                 │  Router  │              │          │
└──────────┘                 └──────────┘              └──────────┘
     │                            │                         │
     │                            ▼                         │
     │                    ┌──────────────┐                 │
     │                    │  Middleware  │                 │
     │                    │  • Auth      │                 │
     │                    │  • Validate  │                 │
     │                    │  • Correlate │                 │
     │                    └──────────────┘                 │
     │                            │                         │
     │                            ▼                         │
     │                    ┌──────────────┐                 │
     │                    │   Handler    │                 │
     │                    │  • Parse     │◀────results─────┘
     │                    │  • Execute   │
     │                    │  • Transform │
     │                    └──────────────┘
     │                            │
     │                            ▼
     │                    ┌──────────────┐
     │◀────response───────│  RFC 7807    │
                          │  Response    │
                          └──────────────┘

Prerequisites #

Required Tools #

Tool Version Purpose
Node.js 22+ Frontend builds, scripts
pnpm 10+ Package management
Deno 2+ Edge Functions runtime
Docker Latest Infrastructure services
Supabase CLI 2.62+ Local backend

mise manages all runtime versions from a single config file:

# Install mise
curl https://mise.run | sh

# Add to shell (zsh example)
echo 'eval "$(mise activate zsh)"' >> ~/.zshrc
source ~/.zshrc

# Install project runtimes
cd backend-utils
mise trust
mise install

# Verify
node -v   # 22.x
deno -v   # 2.x
pnpm -v   # 10.x

Why mise? This project uses two JavaScript runtimes: Node.js for the frontend apps and Deno for Edge Functions. mise ensures everyone uses matching versions without conflicts.

IDE Setup #

For VS Code, add mise shims to your shell profile so the IDE finds the correct runtimes:

# ~/.zshenv (loaded by VS Code)
if command -v mise &>/dev/null; then
  eval "$(mise activate zsh --shims)"
fi

Restart VS Code after adding this.


Quick Start #

1. Clone and Install #

git clone https://github.com/okiki/backend-utils.git
cd backend-utils
pnpm install

2. Environment Setup #

cp .env.example .env.local

Start Supabase first to get the keys:

pnpm start:supabase

Copy the output keys to .env.local:

SUPABASE_URL=http://127.0.0.1:54321
SUPABASE_PUBLIC_KEY=<anon key from output>
SUPABASE_SECRET_KEY=<service_role key from output>

3. Start Services #

Terminal 1 — Backend:

pnpm start:supabase

4. Access Points #

Service URL
Supabase Studio http://127.0.0.1:54323
Supabase API http://127.0.0.1:54321
Email Testing http://127.0.0.1:54324

Testing Edge Functions #

Mental Model: Local Authentication Flow #

┌─────────────────────────────────────────────────────────────────┐
│                    LOCAL TESTING WORKFLOW                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. Start Supabase    2. Create User    3. Sign In             │
│     ┌──────────┐         ┌─────────┐       ┌────────┐          │
│     │ Services │────────▶│ Profile │──────▶│  JWT   │          │
│     │ Running  │         │ Created │       │ Token  │          │
│     └──────────┘         └─────────┘       └────────┘          │
│          │                    │                 │               │
│          │                    │                 ▼               │
│          │                    │         ┌──────────────┐        │
│          │                    │         │ Test Edge    │        │
│          │                    │         │ Functions    │        │
│          │                    │         └──────────────┘        │
│          │                    │                                 │
│          ▼                    ▼                                 │
│  Port 54321 (API)     Port 54324 (Email)                        │
│  Port 54323 (Studio)                                            │
└─────────────────────────────────────────────────────────────────┘

Option 1: Quick Test with Service Role (Development Only) #

For quick local testing without authentication:

# Get keys from Supabase status
SERVICE_ROLE_KEY=$(supabase status | grep "service_role key" | awk '{print $3}')

# Test endpoint (bypasses RLS and authentication)
curl "http://127.0.0.1:54321/functions/v1/social/likes/list" \
  -H "Authorization: Bearer $SERVICE_ROLE_KEY" \
  -H "Content-Type: application/json"

⚠️ Warning: Service role bypasses Row Level Security. Only use for local development.

This mirrors production behavior and tests RLS policies properly.

Step 1: Create a Test User #

Method A: Via Supabase Studio UI

  1. Open http://127.0.0.1:54323
  2. Go to Authentication → Users
  3. Click Add User
  4. Enter email/password, click Create User

Method B: Via Script

# Create test-user.ts
cat > test-user.ts << 'EOF'
import { createClient } from 'jsr:@supabase/supabase-js@2'

const SERVICE_ROLE_KEY = Deno.env.get('SUPABASE_SECRET_KEY')!

const supabase = createClient(
  'http://127.0.0.1:54321',
  SERVICE_ROLE_KEY,
)

// Create test user
const { data, error } = await supabase.auth.admin.createUser({
  email: 'test@example.com',
  password: 'test-password-123',
  email_confirm: true, // Skip email confirmation for local testing
})

if (error) {
  console.error('Error:', error)
  Deno.exit(1)
}

console.log('✅ User created:', data.user.id)
console.log('📧 Email:', data.user.email)
EOF

# Run it
deno run --allow-net --allow-env test-user.ts

Step 2: Sign In and Get JWT #

Method A: Via curl

# Get anon key
ANON_KEY=$(supabase status | grep "anon key" | awk '{print $3}')

# Sign in and get token
curl -s 'http://127.0.0.1:54321/auth/v1/token?grant_type=password' \
  -H "apikey: $ANON_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "test@example.com",
    "password": "test-password-123"
  }' | jq -r '.access_token'

Method B: Via Script

# Create get-token.ts
cat > get-token.ts << 'EOF'
import { createClient } from 'jsr:@supabase/supabase-js@2'

const ANON_KEY = Deno.env.get('SUPABASE_PUBLIC_KEY')!

const supabase = createClient(
  'http://127.0.0.1:54321',
  ANON_KEY,
)

const { data, error } = await supabase.auth.signInWithPassword({
  email: 'test@example.com',
  password: 'test-password-123',
})

if (error) {
  console.error('Error:', error)
  Deno.exit(1)
}

console.log('✅ Signed in')
console.log('📝 Access Token:')
console.log(data.session?.access_token)
EOF

# Run it
deno run --allow-net --allow-env get-token.ts

Step 3: Test Your Endpoint #

# Save the token from Step 2
ACCESS_TOKEN="<token-from-step-2>"

# Test authenticated endpoint
curl "http://127.0.0.1:54321/functions/v1/social/likes/list?limit=10" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json"

Authentication Test Flow Diagram #

┌────────────────────────────────────────────────────────────────┐
│                 AUTHENTICATION TEST FLOW                       │
└────────────────────────────────────────────────────────────────┘

User Creation                Sign In                  API Request
─────────────                ────────                 ───────────

┌──────────┐               ┌──────────┐              ┌──────────┐
│ Studio   │               │  Auth    │              │  Edge    │
│   UI     │──create──────▶│  Server  │──JWT────────▶│ Function │
│    or    │               │          │   token      │          │
│ Script   │               │  POST    │              │  Hono    │
└──────────┘               │  /token  │              │  Router  │
     │                     └──────────┘              └──────────┘
     │                          │                         │
     ▼                          ▼                         ▼
[email/password]            Returns:                 Validates:
[email_confirm:true]        • access_token          • JWT signature
                            • refresh_token         • JWT expiry
                            • user metadata         • RLS policies
                                                    Returns data

Complete Test Script #

Save this as test-endpoint.sh:

#!/bin/bash
set -e

echo "🚀 Testing Edge Function with Authentication"

# Get credentials from Supabase
ANON_KEY=$(supabase status | grep "anon key" | awk '{print $3}')
BASE_URL="http://127.0.0.1:54321"

# Sign in
echo "🔐 Signing in as test@example.com..."
RESPONSE=$(curl -s "${BASE_URL}/auth/v1/token?grant_type=password" \
  -H "apikey: $ANON_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "test@example.com",
    "password": "test-password-123"
  }')

# Extract token
TOKEN=$(echo "$RESPONSE" | jq -r '.access_token')

if [ "$TOKEN" = "null" ] || [ -z "$TOKEN" ]; then
  echo "❌ Error: Failed to get access token"
  echo "$RESPONSE" | jq '.'
  exit 1
fi

echo "✅ Got access token"

# Test endpoint
echo ""
echo "📡 Testing endpoint..."
curl -i "${BASE_URL}/functions/v1/social/likes/list?limit=5" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json"

Make it executable and run:

chmod +x test-endpoint.sh
./test-endpoint.sh

Testing Different Scenarios #

# Test with different query parameters
ACCESS_TOKEN="your-token-here"

# Pagination
curl "http://127.0.0.1:54321/functions/v1/social/likes/list?limit=20&offset=40" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# Filtering
curl "http://127.0.0.1:54321/functions/v1/social/likes/list?filter[user_id][eq]=123" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# Sorting
curl "http://127.0.0.1:54321/functions/v1/social/likes/list?sort=created_at:desc" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# Field selection
curl "http://127.0.0.1:54321/functions/v1/social/likes/list?fields=id,user_id,created_at" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Available Scripts #

All scripts run from the repository root:

# Supabase
pnpm start:supabase   # Start Supabase + Edge Functions
pnpm stop:supabase    # Stop Supabase
pnpm db:types         # Regenerate TypeScript types from schema

Environment Variables #

Copy .env.example to .env.local and configure:

Required #

# Supabase (from `pnpm start:supabase` output)
SUPABASE_URL=http://127.0.0.1:54321
SUPABASE_PUBLIC_KEY=<anon-key>
SUPABASE_SECRET_KEY=<service-role-key>

OAuth (Optional) #

SUPABASE_AUTH_GITHUB_CLIENT_ID=...
SUPABASE_AUTH_GITHUB_CLIENT_SECRET=...
SUPABASE_AUTH_DISCORD_CLIENT_ID=...
SUPABASE_AUTH_DISCORD_CLIENT_SECRET=...
SUPABASE_AUTH_GOOGLE_CLIENT_ID=...
SUPABASE_AUTH_GOOGLE_CLIENT_SECRET=...

Edge Functions #

Function Architecture #

The Deno-based functions are built with Hono and Zod v4:

Function Auth Description
social Yes User likes & follows

Directory Structure #

Each function follows the same pattern:

supabase/functions/my-function/
├── deno.json           # Import map extending _shared
├── index.ts            # App entry point (Hono)
├── mod.ts              # Endpoint definitions registry
├── utils/              # Function-specific utilities
│   └── _schemas.ts
└── endpoints/
    └── my-endpoint/
        ├── _env.ts         # Environment variables
        ├── definition.ts   # Schema + route config
        └── handler.ts      # Request handler

Shared Utilities #

supabase/functions/_shared/ provides:

_shared/
├── middleware/
│   ├── auth.ts         # JWT validation, user context
│   ├── correlation.ts  # Logging & Request ID tracking
│   └── validation.ts   # Zod schema validation
├── query/
│   └── execution/
│       └── supabase.ts # QuerySpec → Supabase queries
├── server/
│   ├── create-app.ts   # Hono app factory
│   └── types.ts        # Shared TypeScript types
└── supabase.ts         # Supabase client factory

Backend utilities in packages/backend/src/:

backend/
├── endpoint/
│   ├── definitions.ts  # Endpoint definition types
│   └── schemas.ts      # Common endpoint schemas
├── query/
│   ├── fields.ts       # Field selection parsing
│   ├── filtering.ts    # Filter parsing and validation
│   ├── pagination.ts   # Cursor/offset pagination
│   ├── query.ts        # QuerySpec builder
│   ├── schemas.ts      # Zod schemas for queries
│   └── sorting.ts      # Sort parsing and validation
└── response/
    ├── errors.ts       # RFC 7807 error responses
    ├── index.ts        # Main exports
    ├── schemas.ts      # Response schemas
    ├── status-codes.ts # HTTP status codes
    └── success.ts      # Success response builders

Creating Functions #

pnpm supabase functions new my-function

This creates supabase/functions/my-function/index.ts and adds config to config.toml.


Database Workflow #

Migrations #

# Create migration
pnpm supabase migration new add_my_table

# Apply migrations (resets local DB)
pnpm supabase db reset

# Push to remote
pnpm supabase db push

Type Generation #

After schema changes, regenerate TypeScript types:

pnpm db:types

Output: packages/shared/src/types/database.types.ts


Working with Deno and Node #

Runtime Context #

Context Runtime Package Manager
Edge Functions Deno 2 JSR imports
Seed scripts Deno 2 JSR imports
Root scripts Node.js pnpm

Import Maps #

deno.jsonc defines shared dependencies:

{
  "imports": {
    "hono": "jsr:@hono/hono@4",
    "zod": "jsr:@zod/zod@4",
    "#shared/": "./supabase/functions/_shared/"
  }
}

Edge Functions import from JSR (Deno's registry). The #shared/ alias provides access to common utilities.


Schema-First Endpoints #

Mental Model: Definition → Execution → Response #

definition.ts          handler.ts             Response
┌────────────────┐     ┌────────────────┐     ┌────────────────┐
│ • Filter rules │ ──▶ │ • Validation   │ ──▶ │ • RFC 7807     │
│ • Sort options │     │ • Execution    │     │ • Pagination   │
│ • Pagination   │     │ • Transform    │     │ • Metadata     │
│ • Output shape │     │                │     │                │
└────────────────┘     └────────────────┘     └────────────────┘
        │                      │                       │
        ▼                      ▼                       ▼
   Type Safety          Runtime Validation      Standardized
   + Documentation      + Error Handling        + Consistent

Endpoint Structure #

endpoints/my-endpoint/
├── _env.ts           # Environment variables (secrets, config)
├── definition.ts     # Schema and route config (the contract)
└── handler.ts        # Request handler (the implementation)

definition.ts — The Contract #

import type { FilterRegistry } from '#shared/server/query/schemas.ts'
import type { EndpointDefinition } from '#shared/server/types.ts'

import { makePaginationResultSchema } from '#shared/response/schemas.ts'
import { createEndpointQuerySchema } from '#shared/server/query/query.ts'
import { z } from 'zod'

import { CURSOR_SECRET } from './_env.ts'
import { BaseQuerySchema } from '#shared/endpoint/schemas.ts'

// 1. Declare which fields can be filtered and how
const filterRegistry: FilterRegistry = {
  publisher_name: { operators: ['eq', 'in'], type: 'string' },
  price: { operators: ['eq', 'gt', 'gte', 'lt', 'lte'], type: 'number' },
} as const

// 2. Create query schema with all rules
const EndpointQuerySchema = createEndpointQuerySchema({
  filters: { registry: filterRegistry, limits: { maxFilters: 20 } },
  sorts: {
    allowedFields: ['price', 'title', 'modified_ts'],
    tiebreaker: 'id',
    defaults: [{ field: 'modified_ts', direction: 'desc' }],
  },
  pagination: {
    cursorSecret: CURSOR_SECRET,
    limits: { defaultLimit: 20, maxLimit: 100 },
  },
})

export const QuerySchema = BaseQuerySchema.transform(value => 
  EndpointQuerySchema.parse(value)
)

// 3. Define output shape
export const NewReleaseItemSchema = z.object({
  uri: z.string(),
  slug: z.string(),
  title: z.string(),
  issue_number: z.string(),
  release_date: z.string(),
  publisher: z.string(),
  cover_image_url: z.string().nullable(),
})

export const OutputSchema = makePaginationResultSchema(
  z.array(NewReleaseItemSchema)
)

// 4. Export definition
export default {
  Name: 'search-comics',
  Route: '/search',
  Methods: ['GET'],
  Input: QuerySchema,
  Output: OutputSchema,
  Schemas: { Query: QuerySchema },
} as const satisfies EndpointDefinition

handler.ts — The Implementation #

import type { 
  EndpointHandler, 
  EndpointMiddlewareHandler, 
  FunctionAppEnv 
} from '#shared/server/types.ts'

import { createValidator } from '#shared/middleware/validation.ts'
import { paginate } from '#shared/response/mod.ts'
import Definition from './definition.ts'

export type AppEnv = FunctionAppEnv

// Middleware validates before handler runs
export const Middleware: EndpointMiddlewareHandler<AppEnv>[] = [
  createValidator('query', Definition.Schemas.Query),
]

// Handler receives typed, validated input
export const Handler: EndpointHandler<AppEnv, typeof Definition> = async (c) => {
  const input = c.req.valid('query')  // Fully typed QuerySpec
  
  // Execute query against backend
  const results = await executeQuery(input)
  
  // Return standardized response
  return c.json(...paginate(url, results, paginationMeta))
}

export default Handler

QuerySpec: Universal Query Language #

Mental Model: Parse → Spec → Execute #

HTTP Query String          QuerySpec             Backend Query
─────────────────          ─────────             ─────────────

filter[price][lte]=5.99    { filters: [          SELECT * FROM comics
sort=title:asc         ──▶   { field: 'price',   WHERE price <= 5.99
limit=20                      operator: 'lte',   ORDER BY title ASC
                              value: 5.99 }      LIMIT 20
                            ],
                            sorts: [...],
                            pagination: {...}
                          }

QuerySpec Structure #

interface QuerySpec {
  pagination: {
    type: 'cursor' | 'offset'
    limit: number
    // ... cursor or offset specific fields
  }
  filters: Array<{
    field: string
    operator: 'eq' | 'in' | 'gt' | 'gte' | 'lt' | 'lte' | ...
    value: any
  }>
  sorts: Array<{
    field: string
    direction: 'asc' | 'desc'
  }>
  fields: {
    type: 'simple'
    fields: string[]
  } | null
}

Backend Translation #

Executors translate QuerySpec to backend-specific queries:

Supabase (PostgreSQL):

supabase
  .from('comics')
  .lte('price', 5.99)
  .order('title', { ascending: true })
  .limit(20)

Typesense (Search):

{
  filter_by: "price:<=5.99",
  sort_by: "title:asc",
  per_page: 20
}

SPARQL (Graph):

SELECT * WHERE {
  ?comic a :Comic .
  ?comic :price ?price .
  FILTER(?price <= 5.99)
}
ORDER BY ?title
LIMIT 20

Response Utilities #

Success Responses #

import { paginate, ok, created } from '#shared/response/mod.ts'

// Collection with pagination
return c.json(...paginate(url, items, paginationMeta))

// Single item
return c.json(...ok(item))

// Created resource
return c.json(...created(item))

Error Responses (RFC 7807) #

import { 
  badRequest, 
  notFound, 
  internalServerError 
} from '#shared/response/mod.ts'

// 400 Bad Request
return c.json(...badRequest(path, 'Invalid filter'))

// 404 Not Found
return c.json(...notFound(path, 'Comic not found'))

// 500 Internal Server Error
return c.json(...internalServerError(path, 'Database connection failed'))

Response Format #

All responses follow RFC 7807 for errors and consistent structure for success:

// Success
{
  "data": [...],
  "pagination": {
    "limit": 20,
    "offset": 0,
    "total": 150
  }
}

// Error
{
  "type": "about:blank",
  "title": "Bad Request",
  "status": 400,
  "detail": "Invalid filter operator 'contains' for field 'price'",
  "instance": "/functions/v1/social/likes/list"
}

Creating a New Endpoint #

Step-by-Step Guide #

1. Create the structure:

mkdir -p supabase/functions/my-function/endpoints/my-endpoint
touch supabase/functions/my-function/endpoints/my-endpoint/{_env.ts,definition.ts,handler.ts}

2. Define environment variables (_env.ts):

export const CURSOR_SECRET = Deno.env.get('CURSOR_SECRET') ?? 'dev-secret'

3. Define the contract (definition.ts):

  • Declare filter registry (which fields, which operators)
  • Configure pagination (cursor/offset, limits)
  • Configure sorting (allowed fields, defaults, tiebreaker)
  • Define output schema (response shape)

4. Implement the handler (handler.ts):

  • Add validation middleware
  • Execute query against backend
  • Return formatted response

5. Register in mod.ts:

import MyEndpoint from './endpoints/my-endpoint/definition.ts'

export const Endpoints = [
  MyEndpoint,
  // ... other endpoints
] as const

6. Wire up in index.ts:

import { Endpoints } from './mod.ts'
import { registerEndpoints } from '#shared/server/create-app.ts'

const app = createApp()
registerEndpoints(app, Endpoints)

Reference Implementation #

See supabase/functions/social/ for a complete reference with:

  • Multiple endpoints (likes, follows)
  • Different HTTP methods (GET, POST, DELETE)
  • Filter/sort/pagination configurations
  • Proper error handling

Deployment #

Supabase (Managed) #

# Link to your project
pnpm supabase link --project-ref <your-project-ref>

# Push database migrations
pnpm supabase db push

# Deploy all functions
pnpm supabase functions deploy

# Deploy specific function
pnpm supabase functions deploy social

Environment Variables in Production #

Set secrets via Supabase CLI:

# Set individual secret
supabase secrets set MY_SECRET=value

# Set from .env file
supabase secrets set --env-file .env.production

# List secrets
supabase secrets list

Troubleshooting #

Port Conflicts #

# Check what's using Supabase ports
lsof -i :54321   # Supabase API
lsof -i :54323   # Supabase Studio
lsof -i :54324   # Email testing (Mailpit)

# Kill process if needed
kill -9 <PID>

Supabase Won't Start #

# Stop and clean up
pnpm supabase stop --no-backup

# Restart
pnpm start:supabase

Type Generation Fails #

Ensure Supabase is running:

# Check status
pnpm supabase status

# If running, regenerate types
pnpm db:types

Edge Function Errors #

# View logs
pnpm supabase functions logs <function-name> --tail

# View logs for all functions
pnpm supabase functions logs --tail

# Test locally first
curl -i "http://127.0.0.1:54321/functions/v1/<function-name>" \
  -H "Authorization: Bearer <token>"

Authentication Issues #

# Verify user exists
psql "postgresql://postgres:postgres@127.0.0.1:54322/postgres" \
  -c "SELECT id, email, created_at FROM auth.users;"

# Check JWT secret
supabase status | grep "JWT secret"

# Verify token is valid
# Use jwt.io to decode and check claims

Database Connection Issues #

# Check database is running
pnpm supabase status

# Test connection
psql "postgresql://postgres:postgres@127.0.0.1:54322/postgres" -c "SELECT version();"

# Reset database (WARNING: destroys data)
pnpm supabase db reset

Tech Stack #

Layer Technology
Edge Functions Deno 2, Hono 4, Zod 4
Database PostgreSQL 17 (Supabase)
Auth Supabase Auth (GitHub, Discord, Google)
Deployment Supabase Cloud
Package Manager pnpm 10 (workspaces)
Runtime Manager mise

Resources #