A Cloudflare Workers-based API for managing 3D printer products, built with Hono, Drizzle ORM, and Cloudflare D1 database.
- Product management (CRUD operations)
- Search functionality with pagination
- Authentication middleware
- Image gallery support
- Stripe integration for payments
- STL file processing and pricing
- Runtime: Cloudflare Workers
- Framework: Hono
- Database: Cloudflare D1 (SQLite)
- ORM: Drizzle ORM
- Authentication: Better Auth with session cookies and passkeys/WebAuthn
- Payment Processing: Stripe
- Validation: Zod
- Testing: Vitest
The API now uses Better Auth for session-based authentication.
- Browser sessions use the
better-auth.session_tokencookie. - Compatibility routes remain available at
/auth/signup,/auth/signin, and/auth/signout. - Native Better Auth routes are mounted under
/api/auth/*.
The API uses the following route protection rules:
- Public read routes: product browsing and read-only catalog endpoints such as
GET /products,GET /products/search,GET /product/:id,GET /categories, and public printer metadata endpoints. - Authenticated user routes: profile endpoints, saved upload endpoints, shipping/payment-intent helpers tied to a signed-in user, and product/category mutation routes such as
POST /add-product,POST /v2/add-product,PUT /update-product,DELETE /delete-product/:id, andPOST /add-category. - Ownership checks: authenticated upload lookup endpoints also enforce that a user can only access their own uploaded files.
When adding new routes, apply authMiddleware directly on the protected route or protected route group before the handler declaration. Do not rely on later middleware registration order.
When the local dev server is running, Better Auth exposes an interactive native API reference at:
http://localhost:8787/api/auth/reference
If you are using the dev server on a different host, use that host with the same path.
Common native auth endpoints exposed by this API include:
GET /api/auth/get-sessionPOST /api/auth/sign-in/emailPOST /api/auth/sign-out
Passkey routes are exposed under /api/auth/passkey/*.
GET /api/auth/passkey/generate-register-optionsPOST /api/auth/passkey/verify-registrationGET /api/auth/passkey/generate-authenticate-optionsPOST /api/auth/passkey/verify-authenticationGET /api/auth/passkey/list-user-passkeys
Notes:
POST /api/auth/passkey/verify-registrationis handled directly by Better Auth.- Validation errors and response payloads for registration verification follow Better Auth defaults.
/api/auth/passkey/registerand/api/auth/passkey/authenticateare client helper names in Better Auth, not server routes in this API.
This project uses Drizzle ORM for database schema management and migrations. Below are detailed instructions for running migrations both locally and on Cloudflare D1.
- Ensure you have the project dependencies installed:
pnpm install - Make sure your local development environment is set up with
.dev.varsfile
When you make changes to the database schema in src/db/schema.ts, generate a migration file:
npx drizzle-kit generateThis will:
- Create a new migration file in
drizzle/migrations/ - Generate the necessary SQL statements based on schema changes
- Update the migration metadata in
drizzle/migrations/meta/
To apply migrations to your local development database:
# Option 1: Apply all pending migrations
npx drizzle-kit migrate
# Option 2: Push schema changes directly (development only)
npx drizzle-kit pushNote: drizzle-kit push is faster for development but doesn't create migration files. Use generate + migrate for production-ready changes.
Start your development server to test the changes:
pnpm run dev- Ensure you're authenticated with Cloudflare:
npx wrangler auth login - Have the correct database name configured in
wrangler.toml
If you prefer to apply changes from the Cloudflare dashboard instead of the CLI:
- Open your D1 database in the Cloudflare dashboard.
- Open the SQL editor for the target database.
- Open the migration file you want to apply from
drizzle/migrations/. - Copy the SQL from the migration file and paste it into the SQL editor.
- Run each migration in chronological order.
- Repeat for every environment you use separately, for example:
- local preview database
- remote production database
- remote preview/staging database
For the Better Auth organization-role migration in this repository, make sure drizzle/migrations/0001_jittery_peter_parker.sql has been applied everywhere the app runs. That migration creates:
organizationmemberinvitationsession.active_organization_id
For each migration file that needs to be applied to the remote database:
npx wrangler d1 execute DATABASE_NAME --remote --file=drizzle/migrations/MIGRATION_FILE.sqlExample:
npx wrangler d1 execute ecommerce --remote --file=drizzle/migrations/0002_uneven_glorian.sqlIf you have multiple migration files to apply, run them in order:
# Apply migrations in chronological order
npx wrangler d1 execute ecommerce --remote --file=drizzle/migrations/0001_initial.sql
npx wrangler d1 execute ecommerce --remote --file=drizzle/migrations/0002_add_column.sql
npx wrangler d1 execute ecommerce --remote --file=drizzle/migrations/0003_update_indexes.sqlFor data cleanup or custom operations:
# Create a temporary SQL file
echo "UPDATE products SET image_gallery = '[]' WHERE image_gallery IS NULL;" > cleanup.sql
# Execute the script
npx wrangler d1 execute ecommerce --remote --file=cleanup.sql
# Clean up
rm cleanup.sqlCheck that your migrations were applied successfully:
# View database schema
npx wrangler d1 execute ecommerce --remote --command="SELECT sql FROM sqlite_master WHERE type='table';"
# Check specific table structure
npx wrangler d1 execute ecommerce --remote --command="PRAGMA table_info(products);"After applying database migrations, deploy your updated application:
pnpm run deploy- Make schema changes in
src/db/schema.ts - Generate migration with
npx drizzle-kit generate - Test locally with
npx drizzle-kit migrateornpx drizzle-kit push - Verify functionality by running
pnpm run dev - Commit changes including both schema and migration files
- Review migration files before applying to production
- Backup database (if applicable) before running migrations
- Apply migrations to remote D1 database using
wrangler d1 execute - Deploy application with
pnpm run deploy - Test endpoints to ensure everything works correctly
- Order matters: Apply migrations in chronological order (0001, 0002, 0003, etc.)
- No rollbacks: D1 doesn't support automatic rollbacks, plan migrations carefully
- Downtime: Remote migrations may cause brief downtime during execution
- Testing: Always test migrations locally before applying to production
- Backup: Consider exporting data before major schema changes
This project now uses Better Auth's organization plugin for catalog-management permissions.
The application uses a single shared organization for catalog administration:
- organization id:
org_shared_catalog - organization name:
3D Printer Web API - organization slug:
3dprinter-web-api
Catalog mutation routes check the caller's shared-organization role, not only the legacy users.role field.
The admin promotion endpoint can promote other users, but the very first admin must exist first.
If you are setting up a fresh environment from the Cloudflare dashboard, do these steps after applying the migrations:
- Find the target user's
idin theuserstable. - Ensure the shared organization row exists in
organization. - Ensure the user has a row in
memberfororg_shared_catalog. - Set that membership's
roletoadmin. - For compatibility with the current transitional code, also set
users.roletoadmin. - Have the user sign out and sign back in so a fresh session is issued.
Recommended values:
organization.id:org_shared_catalogorganization.name:3D Printer Web APIorganization.slug:3dprinter-web-apiorganization.metadata:{"type":"shared"}member.id:member:org_shared_catalog:<USER_ID>member.organization_id:org_shared_catalogmember.user_id:<USER_ID>member.role:admin
After the first admin exists, future promotions and demotions should go through the application endpoint instead of direct database edits:
POST /users/:id/organization-role
Request body:
{
"role": "admin"
}Valid roles for this endpoint are:
adminmember
If Better Auth organization endpoints fail, verify all of the following in the target database:
- the
organizationtable exists - the
membertable exists - the
invitationtable exists - the
sessiontable hasactive_organization_id - the shared organization row exists
- the intended admin user has a
memberrow fororg_shared_catalog - the intended admin user also has
users.role = 'admin'during the transition period
Migration file not found:
# Check if migration files exist
ls -la drizzle/migrations/
# Ensure you're in the project root directory
pwdDatabase connection errors:
# Verify wrangler authentication
npx wrangler auth whoami
# Check database configuration
npx wrangler d1 listSchema sync issues:
# Force regenerate migration
npx drizzle-kit generate --force
# Check current schema state
npx drizzle-kit introspect# Development
npx drizzle-kit generate # Generate migration files
npx drizzle-kit migrate # Apply migrations locally
npx drizzle-kit push # Push schema changes directly (dev only)
npx drizzle-kit introspect # Inspect current database schema
# Production (Cloudflare D1)
npx wrangler d1 list # List databases
npx wrangler d1 execute DB_NAME --remote --file=FILE # Execute migration file
npx wrangler d1 execute DB_NAME --remote --command=SQL # Execute SQL command
npx wrangler d1 export DB_NAME # Export database- Clone the repository
- Install dependencies:
pnpm install - Set up environment variables (copy
.dev.vars.exampleto.dev.vars) - Add a strong
BETTER_AUTH_SECRETand setDOMAINfor local Better Auth callbacks/docs- Example local values:
BETTER_AUTH_SECRET=<random 32+ byte secret>DOMAIN=http://localhost:8787RP_ID=localhost- Optional when frontend runs on another origin (for example
http://localhost:3000):PASSKEY_ORIGIN=http://localhost:3000
- Example local values:
- Run migrations:
npx drizzle-kit push - Start development server:
pnpm run dev
After the dev server starts, you can open:
- App docs:
http://localhost:8787/docs - Better Auth native reference:
http://localhost:8787/api/auth/reference
pnpm run deployNotes:
pnpm run deploydeploys the default Wrangler worker (name = "3dprinter-web-api").- Ensure passkey variables are set in
wrangler.tomlunder[vars]:DOMAIN=https://rc-store.benhalverson.devRP_ID=rc-store.benhalverson.devPASSKEY_ORIGIN=https://rc-store.benhalverson.dev
GET /products- List all products (with optional pagination)GET /products/search- Search products (authenticated, with pagination)GET /product/:id- Get specific productPOST /add-product- Add new product (authenticated)PUT /update-product- Update product (authenticated)POST /auth/signup- Create a user and issue a session cookiePOST /auth/signin- Sign in and issue a session cookieGET|POST /auth/signout- Clear the current session cookieGET /api/auth/get-session- Return the active Better Auth session