{"id":22514671,"url":"https://github.com/jdizm/supabase-express-api","last_synced_at":"2025-08-03T16:31:14.502Z","repository":{"id":224189082,"uuid":"738009677","full_name":"JDIZM/supabase-express-api","owner":"JDIZM","description":"A starter template for a backend API with Node, Express, TypeScript, Drizzle, PotsgreSQL. Authentication with Supabase with role based permissions. Deployment via DigitalOcean docker registry.","archived":false,"fork":false,"pushed_at":"2024-12-06T15:36:36.000Z","size":521,"stargazers_count":5,"open_issues_count":8,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-12-06T16:39:34.890Z","etag":null,"topics":["api","backend","digitalocean","docker","drizzle","drizzle-orm","esm","express","github-actions","node","postgresql","supabase","template","typescript","zod"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JDIZM.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-01-02T07:42:06.000Z","updated_at":"2024-12-03T23:21:30.000Z","dependencies_parsed_at":"2024-02-24T12:25:01.273Z","dependency_job_id":"a7dae0c2-5993-41e4-9625-c0c92f154e31","html_url":"https://github.com/JDIZM/supabase-express-api","commit_stats":null,"previous_names":["jdizm/node-express-esm-backend-component","jdizm/supabase-express-api"],"tags_count":2,"template":true,"template_full_name":"JDIZM/node-express-firebase-mongodb","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JDIZM%2Fsupabase-express-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JDIZM%2Fsupabase-express-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JDIZM%2Fsupabase-express-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JDIZM%2Fsupabase-express-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JDIZM","download_url":"https://codeload.github.com/JDIZM/supabase-express-api/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228553336,"owners_count":17935975,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","backend","digitalocean","docker","drizzle","drizzle-orm","esm","express","github-actions","node","postgresql","supabase","template","typescript","zod"],"created_at":"2024-12-07T03:20:11.113Z","updated_at":"2025-08-03T16:31:14.417Z","avatar_url":"https://github.com/JDIZM.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Supabase Express API\n\nA production-ready Node.js/Express API template with TypeScript, authentication, and multi-tenant workspace system.\n\n## Overview\n\nThis template provides a robust foundation for building scalable APIs with:\n\n- **Authentication** - JWT-based auth with Supabase integration\n- **Multi-tenancy** - Workspace-based system with role-based permissions\n- **Database** - PostgreSQL with Drizzle ORM and migrations\n- **Security** - Helmet, CORS, input validation, and error handling\n- **Developer Experience** - ESM support, hot reload, testing, and Docker\n\n## Features\n\n### Core Features\n\n- 🔐 **Authentication \u0026 Authorization** - JWT bearer tokens with Supabase\n- 🏢 **Workspace Management** - Multi-tenant workspace system\n- 👥 **User Management** - Account creation, profiles, and memberships\n- 🛡️ **Role-based Permissions** - SuperAdmin, Admin, User, and Owner roles\n- 📊 **Database Management** - Migrations, seeding, and Drizzle ORM\n- ⚡ **Real-time Development** - Hot reload with tsx and pkgroll\n\n### API Endpoints\n\n- `/auth` - Authentication (login, signup)\n- `/accounts` - User account management\n- `/workspaces` - Workspace CRUD operations\n- `/profiles` - User profiles within workspaces\n- `/memberships` - Workspace membership management\n\n### Tech Stack\n\n- **Runtime**: Node.js 22+ with ESM modules\n- **Framework**: Express.js with TypeScript\n- **Database**: PostgreSQL with [Drizzle ORM](https://orm.drizzle.team/)\n- **Authentication**: [Supabase](https://supabase.io/) with JWT\n- **Validation**: [Zod](https://zod.dev/) schemas\n- **Testing**: [Vitest](https://vitest.dev/) with coverage\n- **Tooling**: ESLint, Prettier, tsx, pkgroll\n- **Monitoring**: Sentry integration\n- **Security**: Helmet, CORS, input sanitization\n\n## Quick Start\n\n### Prerequisites\n\n- **Node.js 22+** - This project uses [Volta](https://volta.sh/) for Node version management\n- **PostgreSQL** - Either local, cloud, or Supabase\n- **Supabase Account** - For authentication (free tier available)\n\n### Installation\n\n1. **Install Node.js with Volta**:\n\n   ```bash\n   curl https://get.volta.sh | bash\n   ```\n\n2. **Install pnpm**:\n\n   ```bash\n   npm install --global corepack@latest\n   corepack enable pnpm\n   ```\n\n3. **Clone and setup**:\n\n   ```bash\n   git clone \u003crepository-url\u003e\n   cd supabase-express-api\n   cp .env.example .env\n   pnpm install\n   ```\n\n4. **Configure environment** (edit `.env`):\n\n   ```bash\n   # Database\n   POSTGRES_HOST=localhost\n   POSTGRES_USER=postgres\n   POSTGRES_PASSWORD=your-password\n   POSTGRES_DB=your-database\n\n   # Supabase\n   SUPABASE_URL=https://your-project.supabase.co\n   SUPABASE_PK=your-public-key\n   ```\n\n5. **Initialize database**:\n\n   ```bash\n   pnpm run migrate\n   pnpm run seed\n   ```\n\n6. **Start development server**:\n   ```bash\n   pnpm dev\n   ```\n\n## Architecture\n\n### ESM Support\n\nThis project uses **ESM (ECMAScript Modules)** for modern JavaScript imports:\n\n- Development: [tsx](https://github.com/esbuild-kit/tsx) for hot reloading\n- Production: [pkgroll](https://github.com/privatenumber/pkgroll) for bundling\n- Import aliases: `@/` maps to `src/` directory\n\n### Database Schema\n\nMulti-tenant workspace system with:\n\n- **accounts** - User accounts with super admin support\n- **workspaces** - Tenant containers owned by accounts\n- **profiles** - User presence within workspaces\n- **workspace_memberships** - Role-based access control\n\n## Development\n\n### Available Scripts\n\n- `pnpm dev` - Start development server with hot reload\n- `pnpm build` - Build for production\n- `pnpm test` - Run tests with coverage\n- `pnpm lint` - Lint code with ESLint\n- `pnpm format` - Format code with Prettier\n- `pnpm tsc:check` - Type check without compilation\n\n### Testing\n\nUses [Vitest](https://vitest.dev/) for unit testing. Install the [Vitest VS Code extension](https://marketplace.visualstudio.com/items?itemName=ZixuanChen.vitest-explorer) for the best experience.\n\n### Debugging\n\nThis project includes VS Code debugging configurations for TypeScript development.\n\n#### Quick Start Debugging\n\n1. **Set breakpoints** in your TypeScript files\n2. **Open Debug panel** (Cmd+Shift+D)\n3. **Select \"Debug API with tsx\"** from the dropdown\n4. **Press F5** to start debugging\n\n#### Debugging Methods\n\n##### Method 1: Direct TypeScript Debugging (Recommended)\n\nUses tsx to run TypeScript directly without building:\n\n```bash\n# VS Code will run this automatically when you press F5\npnpm tsx src/server.ts\n```\n\n##### Method 2: Attach to Running Process\n\nFor debugging an already running server:\n\n```bash\n# Terminal 1: Start server with inspect flag\npnpm tsx --inspect src/server.ts\n\n# Terminal 2: Or use the provided task\n# Cmd+Shift+P -\u003e \"Tasks: Run Task\" -\u003e \"pnpm: dev with debugging\"\n```\n\nThen in VS Code:\n\n1. Select **\"Attach to Running Server\"** from debug dropdown\n2. Press F5 to attach\n3. Debugger connects to port 9229\n\n##### Method 3: Command Line Debugging\n\nFor debugging without VS Code:\n\n```bash\n# Start with Node.js inspector\nnode --inspect-brk ./node_modules/.bin/tsx src/server.ts\n\n# Chrome DevTools debugging\n# 1. Open chrome://inspect\n# 2. Click \"Open dedicated DevTools for Node\"\n# 3. Server will pause at first line\n```\n\n#### Debugging Features\n\n- **Breakpoints**: Click left of line numbers in VS Code\n- **Conditional Breakpoints**: Right-click breakpoint -\u003e \"Edit Breakpoint\"\n- **Logpoints**: Right-click line -\u003e \"Add Logpoint\" (logs without stopping)\n- **Debug Console**: Evaluate expressions while paused\n- **Call Stack**: See function call hierarchy\n- **Variables**: Inspect local and closure variables\n\n#### Debugging Tips\n\n1. **Profile Name Issue**: Set breakpoints at:\n\n   - `workspaces.handlers.ts:17` - Check if profileName is extracted\n   - `workspaces.handlers.ts:39` - See profile creation\n\n2. **Request Debugging**:\n\n   ```typescript\n   // Add logpoint or breakpoint here\n   console.log(\"Request body:\", req.body);\n   console.log(\"Headers:\", req.headers);\n   ```\n\n3. **Database Queries**:\n\n   ```typescript\n   // Enable query logging\n   const result = await db.select().from(accounts);\n   console.log(\"SQL:\", result.toSQL()); // If using query builder\n   ```\n\n4. **Hot Reload**: Keep debugger attached while making changes - tsx will restart automatically\n\n#### VS Code Debug Configurations\n\nLocated in `.vscode/launch.json`:\n\n- **Debug API with tsx**: Direct TypeScript debugging\n- **Attach to Running Server**: Attach to existing process on port 9229\n\nTasks in `.vscode/tasks.json`:\n\n- **pnpm: dev with debugging**: Start server with inspect flag\n- **pnpm: build/test/lint**: Other development tasks\n\n## Database Setup\n\n### Database Management\n\n- **View Database**: `pnpm run studio` - Opens Drizzle Kit studio\n- **Migrations**: `pnpm run migrate` - Apply pending migrations\n- **Seeding**: `pnpm run seed` - Populate with sample data\n\n### Local Development Options\n\n#### Option 1: Supabase CLI (Recommended)\n\n```bash\n# Install Supabase CLI\npnpm add -D supabase\n\n# Start local Supabase\nsupabase start\n\n# Use connection string in .env\n# postgresql://postgres:postgres@localhost:54322/postgres\n```\n\nAccess dashboard at http://localhost:54323\n\n#### Option 2: Docker PostgreSQL\n\n```bash\n# Simple setup\ndocker compose up -d\n\n# Or with custom network\ndocker network create mynetwork\ndocker run --network mynetwork --name postgres \\\n  -e POSTGRES_PASSWORD=example \\\n  -p 5432:5432 -d postgres:15\n```\n\n### Migrations\n\n#### Initial Setup\n\nWhen running migrations for the first time on a new database:\n\n```bash\npnpm run migrate\n```\n\n#### Schema Changes\n\nWhen you modify the schema/models in `src/schema.ts`:\n\n**1. Generate a new migration:**\n\n```bash\npnpm run migrate:create\n```\n\n**2. Apply the migration:**\n\n```bash\npnpm run migrate\n```\n\n#### Development Workflow\n\nFor rapid development, you can push schema changes directly (skips migration files):\n\n```bash\npnpm run migrate:push\n```\n\n**⚠️ Warning:** `migrate:push` is for development only - it can cause data loss in production.\n\n### Seeds\n\nThe seed script creates a comprehensive multi-tenant test environment with realistic business scenarios.\n\n#### Seed Options\n\n```bash\n# Create local accounts only (recommended for development)\npnpm run seed\n\n# Create both local accounts AND Supabase auth users\npnpm run seed --supabase=true\n```\n\n#### Test Data Created\n\n**8 Test Accounts:**\n\n- `admin@example.com` - Super Admin (can access admin endpoints)\n- `alice@acmecorp.com` - ACME Corp owner with 2 workspaces\n- `bob@techstartup.com` - TechStartup owner with 2 workspaces\n- `carol@designstudio.com` - Design Studio owner\n- `david@acmecorp.com` - ACME employee (user role)\n- `emma@techstartup.com` - TechStartup employee (admin/user roles)\n- `frank@suspended.com` - Suspended account (testing)\n- `grace@inactive.com` - Inactive account (testing)\n\n**5 Realistic Workspaces:**\n\n- \"ACME Corp - Main\" - Primary business workspace\n- \"ACME Corp - R\u0026D\" - Research \u0026 development\n- \"TechStartup - Development\" - Software development\n- \"TechStartup - Marketing\" - Marketing campaigns\n- \"Design Studio Pro\" - Creative workspace\n\n**Multi-tenant Scenarios:**\n\n- Cross-workspace memberships (Alice, Bob, Emma in multiple workspaces)\n- Different roles within organizations (admin/user)\n- Cross-company collaboration (Emma consulting for ACME)\n- Account status variations (active/suspended/inactive)\n\n#### Supabase Integration\n\nFor **database/API testing**: Use default `pnpm run seed` (local accounts only)\n\nFor **authentication testing**: Use `pnpm run seed --supabase=true` and either:\n\n- Disable email confirmation in Supabase Auth settings, OR\n- Manually confirm users in Supabase dashboard after seeding\n- Replace test emails with working emails in `src/services/db/seeds/accounts.ts`\n\n#### Development Workspace Setup\n\nAfter seeding the database, you can create development workspaces for testing:\n\n```bash\n# Create a single workspace\npnpm dev:workspace --email=alice@acmecorp.com --name=\"Test Workspace\"\n\n# Create a workspace with specific profile name and role\npnpm dev:workspace --email=david@acmecorp.com --name=\"Client Project\" --profile=\"David Chen\" --role=user\n\n# Create multiple test workspaces\npnpm dev:workspaces --email=bob@techstartup.com\n```\n\n**Note**: The account email must exist in the database (created during seeding) before creating workspaces.\n\n#### JWT Token Testing\n\nTest and generate JWT tokens for API development and debugging:\n\n```bash\n# Generate a test token for development (use actual account ID from seeded data)\npnpm token-test --generate --account-id=\u003caccount-uuid\u003e --email=alice@acmecorp.com\n\n# Verify a token with full payload information\npnpm token-test --token=\u003cjwt-token\u003e --show-payload --check-expiry\n\n# Test if your JWT secret works with a token\npnpm token-test --token=\u003cjwt-token\u003e --test-secret\n\n# Decode token without verification (debugging)\npnpm token-test --token=\u003cjwt-token\u003e --decode-only\n```\n\n**Note**: Use actual account IDs from your seeded database when generating tokens.\n\nBe sure to update the seeds as new migrations are added.\n\n## Build with docker\n\n```bash\n# build the app\npnpm build\n\n# build with docker\ndocker build . --tag node-express\n\n# or to build with a specific platform\ndocker build . --tag node-express --platform linux/amd64\n\n# or build a specific stage eg dev\ndocker build . --target dev --tag node-express\n\n# start the docker container\ndocker run -d -p 4000:4000 node-express\n\n# view it running on localhost\ncurl localhost:4000\n```\n\n## Import aliases\n\nAliases can be configured in the import map, defined in package.json#imports.\n\nsee: https://github.com/privatenumber/pkgroll#aliases\n\n## Authentication\n\nThis project uses JWT bearer token for authentication. The claims, id and sub must be set on the token and the token can be verified and decoded using the configured auth provider.\n\n## Permissions\n\nHow permissions work.\n\nA resource will have a permission level for each route method based on users role within the workspace. Workspace permissions can be defined in `./src/helpers/permissions.ts`.\n\nWorkspace level permissions:\nAdmin: Highest level of access to all resources within the workspace.\nUser: Regular user with limited permissions.\n\nResource level permissions:\nOwner: Has access to their own resources\n\nAccount level permissions:\nSuperAdmin: Has access to all super only resources.\n\n### Workspace Authorization Pattern\n\nThis API uses a consistent header-based authorization pattern for all workspace-scoped operations.\n\n#### The `x-workspace-id` Header\n\nAll requests that operate within a workspace context **must** include the `x-workspace-id` header, even if the workspace ID is already present in the URL path.\n\n**Why use headers instead of just URL parameters?**\n\n- **Consistency**: Single authorization pattern across all endpoints\n- **Flexibility**: Supports future endpoints that don't naturally include workspace ID in the URL\n- **Security**: Explicit workspace context prevents accidental cross-workspace access\n- **Scalability**: Easy to add additional context headers in the future (e.g., `x-project-id`)\n\n**Example:**\n\n```bash\n# Even though the workspace ID is in the URL, the header is still required\ncurl -X GET http://localhost:4000/workspaces/123e4567-e89b-12d3-a456-426614174000/members \\\n  -H \"Authorization: Bearer your-jwt-token\" \\\n  -H \"x-workspace-id: 123e4567-e89b-12d3-a456-426614174000\"\n```\n\nThe authorization middleware will:\n\n1. Verify the user is authenticated (via JWT)\n2. Check the user is a member of the specified workspace (via header)\n3. Validate the user has the required role (User/Admin) for the operation\n\nA role/claim is defined when the account is added to the workspace as a member.\n\n1. User - Can access all resources with user permissions.\n2. Admin - Can access all resources within the workspace.\n\n### API Endpoints\n\n#### Profile Data Access\n\nProfile endpoints (`/profiles` and `/profiles/:id`) have been removed to enforce proper workspace-scoped security. Profile data is now accessible only through workspace context:\n\n- **`GET /me`** - Returns the current user's account and all their profiles across workspaces\n- **`GET /workspaces/:id`** - Returns workspace details including all members with their profiles\n- **`GET /workspaces/:id/members`** - Returns all workspace members with profile information\n\nThis ensures profile data is always accessed with proper workspace authorization.\n\n## Supabase Auth\n\nsee the [documentation for more information](https://supabase.com/docs/reference/javascript/auth-api) on how to use Supabase Auth with this project.\n\n## Deployment with DigitalOcean\n\nA docker image can be built and deployed to a [container registry](https://docs.digitalocean.com/products/container-registry/getting-started/quickstart/). We can configure DigitalOcean to deploy the image once the registry updates using their [App Platform](https://docs.digitalocean.com/products/app-platform/)\n\nThe following secrets will need to be added to Github Actions for a successful deployment to DigitalOcean.\n\n### Environment variables for deployment\n\n- `DIGITALOCEAN_ACCESS_TOKEN` https://docs.digitalocean.com/reference/api/create-personal-access-token/\n- `REGISTRY_NAME` eg registry.digitalocean.com/my-container-registry\n- `IMAGE_NAME` the name of the image we are pushing to the repository eg `express-api` it will be tagged with the latest version and a github sha.\n\n### App level environment variables\n\nFor information on confguring the app level environment variables see [How to use environment variables in DigitalOcean App Platform](https://docs.digitalocean.com/products/app-platform/how-to/use-environment-variables/)\n\n- `NODE_ENV`: `production`\n- `APP_URL`: `https://api.example.com`\n- `POSTGRES_HOST`: `\u003cregion\u003e.pooler.supabase.com`\n- `POSTGRES_USER`: `postgres.\u003csupabase-id\u003e`\n- `POSTGRES_PASSWORD`: `example`\n- `POSTGRES_DB`: `postgres`\n- `SUPABASE_URL`: `https://\u003csupabase-id\u003e.supabase.co`\n- `SUPABASE_PK`: `abcdefghijklm`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjdizm%2Fsupabase-express-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjdizm%2Fsupabase-express-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjdizm%2Fsupabase-express-api/lists"}