Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions examples/vercel/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Dependencies
node_modules

# Build outputs
dist
api/_handler.js
api/_handler.js.map

# Environment variables
.env
.env.local
.env.production

# Vercel
.vercel

# OS
.DS_Store
3 changes: 3 additions & 0 deletions examples/vercel/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# pnpm configuration for Vercel deployment
# Use hoisted node_modules to avoid symlink issues in serverless functions
node-linker=hoisted
164 changes: 164 additions & 0 deletions examples/vercel/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# Vercel Deployment Example

Deploy an ObjectStack server with Hono to Vercel.

## Features

- ✅ Hono adapter for fast, edge-compatible API routes
- ✅ Turso/LibSQL database driver with in-memory fallback
- ✅ Authentication with better-auth
- ✅ Security plugin for RBAC
- ✅ Optimized serverless function bundling with esbuild
- ✅ Environment-based configuration

## Prerequisites

1. A [Vercel](https://vercel.com) account
2. A [Turso](https://turso.tech) database (optional, uses in-memory storage if not configured)

## Local Development

```bash
# Install dependencies (from monorepo root)
pnpm install

# Start local development server
cd examples/vercel
pnpm dev
```

The server will be available at `http://localhost:3000/api/v1`.

## Deployment to Vercel

### Option 1: Deploy via Vercel CLI

```bash
# Install Vercel CLI
npm i -g vercel

# Deploy from the examples/vercel directory
cd examples/vercel
vercel
```

### Option 2: Deploy via Vercel Dashboard

1. Import your GitHub repository in the [Vercel Dashboard](https://vercel.com/new)
2. Set the **Root Directory** to `examples/vercel`
3. Configure environment variables (see below)
4. Click **Deploy**

## Environment Variables

Configure these in your Vercel project settings:

| Variable | Description | Required | Example |
|----------|-------------|----------|---------|
| `TURSO_DATABASE_URL` | Turso database connection URL | No* | `libsql://your-db.turso.io` |
| `TURSO_AUTH_TOKEN` | Turso authentication token | No* | `eyJ...` |
| `AUTH_SECRET` | Secret key for authentication (min 32 chars) | Yes | Generate with `openssl rand -base64 32` |

*If not set, the server will use an in-memory database (data will be lost on restart).

### Setting up Turso Database

```bash
# Install Turso CLI
curl -sSfL https://get.tur.so/install.sh | bash

# Create a new database
turso db create objectstack-vercel

# Get the database URL
turso db show objectstack-vercel --url

# Create an auth token
turso db tokens create objectstack-vercel

# Add both values to Vercel environment variables
```

## Project Structure

```
examples/vercel/
├── api/
│ └── [[...route]].js # Vercel serverless function entry point
├── scripts/
│ ├── bundle-api.mjs # esbuild bundler for serverless function
│ └── build-vercel.sh # Vercel build script
├── server/
│ └── index.ts # Server entrypoint with kernel bootstrap
├── objectstack.config.ts # ObjectStack configuration
├── package.json
├── tsconfig.json
├── vercel.json # Vercel deployment configuration
└── README.md
```

## How It Works

1. **Build Step**: `scripts/build-vercel.sh` runs on Vercel, which:
- Builds the monorepo using turbo
- Bundles `server/index.ts` → `api/_handler.js` using esbuild

2. **Runtime**: Vercel routes requests to `api/[[...route]].js`, which:
- Lazily boots the ObjectStack kernel on first request
- Delegates to the Hono adapter for request handling
- Persists kernel state across warm invocations

3. **Database**:
- Production: Uses Turso (edge-compatible LibSQL)
- Local dev: Falls back to in-memory driver

## API Routes

All ObjectStack API routes are available under `/api/v1`:

- `GET /api/v1/meta` - Metadata discovery
- `GET /api/v1/data/:object` - Query data
- `POST /api/v1/data/:object` - Insert records
- `PATCH /api/v1/data/:object/:id` - Update records
- `DELETE /api/v1/data/:object/:id` - Delete records
- `POST /api/v1/auth/sign-in` - Authentication
- And more...

## Testing the Deployment

```bash
# Health check
curl https://your-deployment.vercel.app/api/v1/meta

# Example API request (after authentication)
curl https://your-deployment.vercel.app/api/v1/data/users \
-H "Authorization: Bearer YOUR_TOKEN"
```

## Troubleshooting

### Build fails with "Module not found"

Make sure you're running the build from the monorepo root, or that Vercel's `installCommand` is set correctly in `vercel.json`.

### Database connection issues

- Verify `TURSO_DATABASE_URL` and `TURSO_AUTH_TOKEN` are set correctly
- Check Turso database is accessible from Vercel's network
- For debugging, you can temporarily use `:memory:` as the database URL

### Cold start timeout

- Increase `maxDuration` in `vercel.json` if needed
- Consider using Vercel Pro for higher limits

## Learn More

- [ObjectStack Documentation](https://docs.objectstack.dev)
- [Hono Vercel Deployment Guide](https://vercel.com/docs/frameworks/backend/hono)
- [Turso Documentation](https://docs.turso.tech)
- [Vercel Serverless Functions](https://vercel.com/docs/functions/serverless-functions)

## License

Apache-2.0
9 changes: 9 additions & 0 deletions examples/vercel/api/[[...route]].js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Vercel Serverless Function — Catch-all API route.
//
// This file MUST be committed to the repository so Vercel can detect it
// as a serverless function during the pre-build phase.
//
// It delegates to the esbuild bundle (`_handler.js`) generated by
// `scripts/bundle-api.mjs` during the Vercel build step.

export { default, config } from './_handler.js';
88 changes: 88 additions & 0 deletions examples/vercel/objectstack.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.

import { defineStack } from '@objectstack/spec';
import { AppPlugin, DriverPlugin } from '@objectstack/runtime';

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused import AppPlugin.
import { ObjectQLPlugin } from '@objectstack/objectql';
import { TursoDriver } from '@objectstack/driver-turso';
import { InMemoryDriver } from '@objectstack/driver-memory';
import { AuthPlugin } from '@objectstack/plugin-auth';
import { SecurityPlugin } from '@objectstack/plugin-security';
import { MetadataPlugin } from '@objectstack/metadata';

/**
* Vercel Deployment Example
*
* This example demonstrates how to deploy an ObjectStack server to Vercel
* using the Hono adapter. It includes:
*
* - TursoDriver for production (with fallback to in-memory for local dev)
* - Authentication with better-auth (environment-based configuration)
* - Security plugin for RBAC
* - Metadata plugin for runtime metadata management
*
* Environment Variables (set in Vercel dashboard or .env.local):
* - TURSO_DATABASE_URL: Turso database connection URL (or ":memory:" for local)
* - TURSO_AUTH_TOKEN: Turso authentication token (optional for local)
* - AUTH_SECRET: Secret key for authentication (min 32 characters)
* - VERCEL_URL: Auto-injected by Vercel (deployment URL)
* - VERCEL_PROJECT_PRODUCTION_URL: Auto-injected by Vercel (production URL)
*/

// Determine if we're running in production (Vercel) or local dev
const isProduction = process.env.VERCEL === '1';

// Database driver: Use Turso in production, in-memory for local dev
const driver = isProduction || process.env.TURSO_DATABASE_URL
? new TursoDriver({
url: process.env.TURSO_DATABASE_URL ?? ':memory:',
...(process.env.TURSO_AUTH_TOKEN && { authToken: process.env.TURSO_AUTH_TOKEN }),
})
: new InMemoryDriver();

// Base URL for authentication (auto-detected from Vercel environment)
const baseUrl = process.env.VERCEL_PROJECT_PRODUCTION_URL
? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`
: process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
: 'http://localhost:3000';

// Collect trusted origins for CORS and CSRF protection
function getVercelOrigins(): string[] {
const origins: string[] = [];
if (process.env.VERCEL_URL) {
origins.push(`https://${process.env.VERCEL_URL}`);
}
if (process.env.VERCEL_BRANCH_URL) {
origins.push(`https://${process.env.VERCEL_BRANCH_URL}`);
}
if (process.env.VERCEL_PROJECT_PRODUCTION_URL) {
origins.push(`https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`);
}
return origins;
}

const trustedOrigins = getVercelOrigins();

export default defineStack({
manifest: {
id: 'com.example.vercel',
namespace: 'vercel',
name: 'Vercel Deployment Example',
version: '1.0.0',
description: 'Example application demonstrating Hono deployment to Vercel',
type: 'app',
},

// Core plugins required for a functional ObjectStack server
plugins: [
new ObjectQLPlugin(),
new DriverPlugin(driver),
new AuthPlugin({
secret: process.env.AUTH_SECRET ?? 'dev-secret-please-change-in-production-min-32-chars',
baseUrl,
...(trustedOrigins.length > 0 ? { trustedOrigins } : {}),
}),
new SecurityPlugin(),
new MetadataPlugin({ watch: false }), // Disable file watching on Vercel
],
});
32 changes: 32 additions & 0 deletions examples/vercel/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "@example/vercel",
"version": "4.0.3",
"description": "Example: Deploy ObjectStack server with Hono to Vercel",
"license": "Apache-2.0",
"private": true,
"type": "module",
"scripts": {
"dev": "hono dev",
"build": "bash scripts/build-vercel.sh",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@hono/node-server": "^1.19.14",
"@objectstack/core": "workspace:*",
"@objectstack/driver-memory": "workspace:*",
"@objectstack/driver-turso": "workspace:*",
"@objectstack/hono": "workspace:*",
"@objectstack/metadata": "workspace:*",
"@objectstack/objectql": "workspace:*",
"@objectstack/plugin-auth": "workspace:*",
"@objectstack/plugin-security": "workspace:*",
"@objectstack/runtime": "workspace:*",
"@objectstack/spec": "workspace:*",
"hono": "^4.12.12"
},
"devDependencies": {
"@types/node": "^22.14.3",
"esbuild": "^0.28.0",
"typescript": "^6.0.2"
}
}
22 changes: 22 additions & 0 deletions examples/vercel/scripts/build-vercel.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -euo pipefail

# Build script for Vercel deployment of ObjectStack server with Hono.
#
# This script:
# 1. Builds the monorepo from the root using turbo
# 2. Bundles the serverless function using esbuild
#
# The bundled function is self-contained and ready for Vercel deployment.

echo "[build-vercel] Starting build..."

# 1. Build the monorepo from the root
cd ../..
pnpm turbo run build --filter=@example/vercel
cd examples/vercel

# 2. Bundle API serverless function
node scripts/bundle-api.mjs

echo "[build-vercel] Done. Serverless function ready at api/_handler.js"
49 changes: 49 additions & 0 deletions examples/vercel/scripts/bundle-api.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Pre-bundles the Vercel serverless API function.
*
* This script bundles server/index.ts with dependencies inlined,
* creating a self-contained serverless function for Vercel deployment.
*
* Native packages like better-sqlite3 are kept external and will be
* packaged separately by Vercel.
*/

import { build } from 'esbuild';

// Packages that cannot be bundled (native bindings / optional drivers)
const EXTERNAL = [
'better-sqlite3',
'@libsql/client',
// Optional knex database drivers
'pg',
'pg-native',
'pg-query-stream',
'mysql',
'mysql2',
'sqlite3',
'oracledb',
'tedious',
// macOS-only native file watcher
'fsevents',
];

await build({
entryPoints: ['server/index.ts'],
bundle: true,
platform: 'node',
format: 'esm',
target: 'es2020',
outfile: 'api/_handler.js',
sourcemap: true,
external: EXTERNAL,
logOverride: { 'require-resolve-not-external': 'silent' },
banner: {
js: [
'// Bundled by esbuild — see scripts/bundle-api.mjs',
'import { createRequire } from "module";',
'const require = createRequire(import.meta.url);',
].join('\n'),
},
});

console.log('[bundle-api] Bundled server/index.ts → api/_handler.js');
Loading
Loading