Skip to content

Commit 31ac053

Browse files
authored
Merge pull request #1120 from objectstack-ai/claude/deploy-example-app-host-vercel
feat: add Vercel deployment support to app-host example using Hono
2 parents 3efac6b + 0d115e8 commit 31ac053

File tree

12 files changed

+1032
-100
lines changed

12 files changed

+1032
-100
lines changed

examples/app-host/.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Turso Database Configuration
2+
# Required for Vercel deployment
3+
TURSO_DATABASE_URL=libsql://your-database.turso.io
4+
TURSO_AUTH_TOKEN=your-auth-token-here

examples/app-host/.gitignore

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Build artifacts
2+
dist/
3+
.turbo/
4+
5+
# Bundled API handler (generated during Vercel build)
6+
api/_handler.js
7+
api/_handler.js.map
8+
9+
# Node modules
10+
node_modules/
11+
12+
# Environment files
13+
.env
14+
.env.local
15+
.env.*.local
16+
17+
# OS files
18+
.DS_Store
19+
Thumbs.db

examples/app-host/.vercelignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Ignore build artifacts
2+
dist/
3+
.turbo/
4+
5+
# Ignore test files
6+
test/
7+
*.test.ts
8+
*.spec.ts
9+
10+
# Ignore development-only files that are not required by the Vercel build
11+
debug-registry.ts
12+
13+
# Keep only the bundled API handler
14+
!api/_handler.js
15+
!api/_handler.js.map
16+
!api/[[...route]].js

examples/app-host/DEPLOYMENT.md

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# Deploying App Host to Vercel
2+
3+
This example demonstrates how to deploy the ObjectStack app-host to Vercel using Hono.
4+
5+
## Prerequisites
6+
7+
1. A Vercel account
8+
2. Vercel CLI installed (optional): `npm i -g vercel`
9+
10+
## Environment Variables
11+
12+
Set the following environment variables in your Vercel project:
13+
14+
- `AUTH_SECRET`: A secure random string (minimum 32 characters) for authentication
15+
- `TURSO_DATABASE_URL`: Your Turso database URL (e.g., `libsql://your-database.turso.io`)
16+
- `TURSO_AUTH_TOKEN`: Your Turso authentication token
17+
18+
You can get these credentials from [Turso Dashboard](https://turso.tech/).
19+
20+
## Deployment Steps
21+
22+
### Option 1: Using Vercel CLI
23+
24+
1. Install Vercel CLI:
25+
```bash
26+
npm i -g vercel
27+
```
28+
29+
2. Navigate to the app-host directory:
30+
```bash
31+
cd examples/app-host
32+
```
33+
34+
3. Deploy to Vercel:
35+
```bash
36+
vercel
37+
```
38+
39+
4. Set environment variables:
40+
```bash
41+
vercel env add AUTH_SECRET
42+
vercel env add TURSO_DATABASE_URL
43+
vercel env add TURSO_AUTH_TOKEN
44+
```
45+
46+
### Option 2: Using Vercel Dashboard
47+
48+
1. Import the repository to Vercel
49+
2. Set the root directory to `examples/app-host`
50+
3. Add environment variables in the project settings
51+
4. Deploy
52+
53+
## Build Configuration
54+
55+
The build is configured in `vercel.json`:
56+
57+
- **Install Command**: `cd ../.. && pnpm install` (installs monorepo dependencies)
58+
- **Build Command**: `bash scripts/build-vercel.sh` (builds and bundles the application)
59+
- **Framework**: `hono` (uses Vercel's Hono framework preset)
60+
61+
## How It Works
62+
63+
1. **Build Process** (`scripts/build-vercel.sh`):
64+
- Builds the TypeScript project using Turbo
65+
- Bundles the server code using esbuild (`scripts/bundle-api.mjs`)
66+
67+
2. **API Handler** (`api/[[...route]].js`):
68+
- Committed catch-all route handler that Vercel detects pre-build
69+
- Delegates to the bundled handler (`api/_handler.js`)
70+
71+
3. **Server Entrypoint** (`server/index.ts`):
72+
- Boots ObjectStack kernel with Hono adapter
73+
- Uses `@hono/node-server`'s `getRequestListener()` for Vercel compatibility
74+
- Connects to Turso database in remote mode (HTTP-only, no local SQLite)
75+
- Handles Vercel's pre-buffered request body properly
76+
77+
4. **Hono Integration**:
78+
- Uses `@objectstack/hono` adapter to create the HTTP application
79+
- Provides REST API at `/api/v1` prefix
80+
- Includes authentication via AuthPlugin
81+
82+
## Architecture
83+
84+
The deployment follows Vercel's serverless function pattern:
85+
86+
```
87+
examples/app-host/
88+
├── api/
89+
│ ├── [[...route]].js # Committed entry point
90+
│ └── _handler.js # Generated bundle (not committed)
91+
├── server/
92+
│ └── index.ts # Server implementation
93+
├── scripts/
94+
│ ├── build-vercel.sh # Build script
95+
│ └── bundle-api.mjs # Bundler configuration
96+
├── .npmrc # pnpm configuration (node-linker=hoisted)
97+
└── vercel.json # Vercel configuration
98+
```
99+
100+
## Testing Locally
101+
102+
Before deploying, you can test locally:
103+
104+
```bash
105+
# Build the application
106+
pnpm build
107+
108+
# Run in development mode
109+
pnpm dev
110+
111+
# Test the API
112+
curl http://localhost:3000/api/v1/discovery
113+
```
114+
115+
## Accessing the API
116+
117+
After deployment, your API will be available at:
118+
119+
- Discovery: `https://your-app.vercel.app/api/v1/discovery`
120+
- Data API: `https://your-app.vercel.app/api/v1/data/:object`
121+
- Meta API: `https://your-app.vercel.app/api/v1/meta/:type`
122+
123+
## Troubleshooting
124+
125+
### Build Fails
126+
127+
- Ensure all dependencies are installed: `pnpm install`
128+
- Check build logs in Vercel dashboard
129+
- Verify `build-vercel.sh` is executable: `chmod +x scripts/build-vercel.sh`
130+
131+
### Runtime Errors
132+
133+
- Check function logs in Vercel dashboard
134+
- Verify environment variables are set correctly (`TURSO_DATABASE_URL`, `TURSO_AUTH_TOKEN`, `AUTH_SECRET`)
135+
- Ensure `AUTH_SECRET` is at least 32 characters
136+
- Test Turso connection using the provided credentials
137+
138+
### Database Connection Issues
139+
140+
- Verify your Turso database URL and auth token are correct
141+
- Check that your Turso database is accessible (not paused)
142+
- The deployment uses TursoDriver in **remote mode** (HTTP-only), which doesn't require native modules like better-sqlite3
143+
144+
## References
145+
146+
- [Vercel Hono Documentation](https://vercel.com/docs/frameworks/backend/hono)
147+
- [ObjectStack Documentation](../../README.md)
148+
- [Hono Documentation](https://hono.dev/)

examples/app-host/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,20 @@
33
This is a reference implementation of the ObjectStack Server Protocol (Kernel).
44
It demonstrates how to build a metadata-driven backend that dynamically loads object definitions from plugins and automatically generates REST APIs.
55

6+
## Deployment
7+
8+
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/objectstack-ai/framework/tree/main/examples/app-host&project-name=objectstack-app-host&repository-name=objectstack-app-host)
9+
10+
See [DEPLOYMENT.md](./DEPLOYMENT.md) for detailed deployment instructions.
11+
612
## Features
713

814
- **Dynamic Schema Loading**: Loads `crm` and `todo` apps as plugins.
915
- **Unified Metadata API**: `/api/v1/meta/objects`
1016
- **Unified Data API**: `/api/v1/data/:object` (CRUD)
1117
- **Zero-Code Backend**: No creating routes or controllers per object.
1218
- **Preview Mode**: Run in demo mode — bypass login, auto-simulate admin identity.
19+
- **Vercel Deployment**: Ready-to-deploy to Vercel with Hono adapter.
1320

1421
## Setup
1522

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Vercel Serverless Function — Catch-all API route.
2+
//
3+
// This file MUST be committed to the repository so Vercel can detect it
4+
// as a serverless function during the pre-build phase.
5+
//
6+
// It delegates to the esbuild bundle (`_handler.js`) generated by
7+
// `scripts/bundle-api.mjs` during the Vercel build step. A separate
8+
// bundle file is used (rather than overwriting this file) so that:
9+
// 1. Vercel always finds this committed entry point (no "File not found").
10+
// 2. Vercel does not TypeScript-compile a .ts stub that references
11+
// source files absent at runtime (no ERR_MODULE_NOT_FOUND).
12+
//
13+
// @see ../server/index.ts — the actual server entrypoint
14+
// @see ../scripts/bundle-api.mjs — the esbuild bundler
15+
16+
export { default, config } from './_handler.js';

examples/app-host/package.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"name": "@example/app-host",
33
"version": "4.0.3",
44
"license": "Apache-2.0",
5+
"type": "module",
56
"private": true,
67
"scripts": {
78
"dev": "objectstack serve --dev",
@@ -16,16 +17,22 @@
1617
"@example/app-crm": "workspace:*",
1718
"@example/app-todo": "workspace:*",
1819
"@example/plugin-bi": "workspace:*",
20+
"@hono/node-server": "^1.19.14",
21+
"@libsql/client": "^0.14.0",
1922
"@objectstack/driver-memory": "workspace:*",
23+
"@objectstack/driver-turso": "workspace:*",
24+
"@objectstack/hono": "workspace:*",
2025
"@objectstack/metadata": "workspace:*",
2126
"@objectstack/objectql": "workspace:*",
2227
"@objectstack/plugin-auth": "workspace:*",
2328
"@objectstack/plugin-hono-server": "workspace:*",
2429
"@objectstack/runtime": "workspace:*",
25-
"@objectstack/spec": "workspace:*"
30+
"@objectstack/spec": "workspace:*",
31+
"hono": "^4.12.12"
2632
},
2733
"devDependencies": {
2834
"@objectstack/cli": "workspace:*",
35+
"esbuild": "^0.24.2",
2936
"ts-node": "^10.9.2",
3037
"tsx": "^4.21.0",
3138
"typescript": "^6.0.2"
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Build script for Vercel deployment of @example/app-host.
5+
#
6+
# Follows the Vercel deployment pattern:
7+
# - api/[[...route]].js is committed to the repo (Vercel detects it pre-build)
8+
# - esbuild bundles server/index.ts → api/_handler.js (self-contained bundle)
9+
# - The committed .js wrapper re-exports from _handler.js at runtime
10+
#
11+
# Steps:
12+
# 1. Build the project with turbo
13+
# 2. Bundle the API serverless function (→ api/_handler.js)
14+
# 3. Copy native/external modules into local node_modules for Vercel packaging
15+
16+
echo "[build-vercel] Starting app-host build..."
17+
18+
# 1. Build the project with turbo (from monorepo root)
19+
cd ../..
20+
pnpm turbo run build --filter=@example/app-host
21+
cd examples/app-host
22+
23+
# 2. Bundle API serverless function
24+
node scripts/bundle-api.mjs
25+
26+
echo "[build-vercel] Done. Serverless function in api/[[...route]].js → api/_handler.js"
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* Pre-bundles the Vercel serverless API function.
3+
*
4+
* Vercel's @vercel/node builder resolves pnpm workspace packages via symlinks,
5+
* which can cause esbuild to resolve to TypeScript source files rather than
6+
* compiled dist output — producing ERR_MODULE_NOT_FOUND at runtime.
7+
*
8+
* This script bundles server/index.ts with ALL dependencies inlined (including
9+
* npm packages), so the deployed function is self-contained. Only packages
10+
* with native bindings are kept external.
11+
*
12+
* Run from the examples/app-host directory during the Vercel build step.
13+
*/
14+
15+
import { build } from 'esbuild';
16+
17+
// Packages that cannot be bundled (native bindings / optional drivers)
18+
const EXTERNAL = [
19+
// Optional knex database drivers — never used at runtime, but knex requires() them
20+
'pg',
21+
'pg-native',
22+
'pg-query-stream',
23+
'mysql',
24+
'mysql2',
25+
'sqlite3',
26+
'oracledb',
27+
'tedious',
28+
// macOS-only native file watcher
29+
'fsevents',
30+
];
31+
32+
await build({
33+
entryPoints: ['server/index.ts'],
34+
bundle: true,
35+
platform: 'node',
36+
format: 'esm',
37+
target: 'es2020',
38+
outfile: 'api/_handler.js',
39+
sourcemap: true,
40+
external: EXTERNAL,
41+
// Silence warnings about optional/unused require() calls in knex drivers
42+
logOverride: { 'require-resolve-not-external': 'silent' },
43+
// Vercel resolves ESM .js files correctly when "type": "module" is set.
44+
// CJS format would conflict with the project's "type": "module" setting,
45+
// causing Node.js to fail parsing require()/module.exports as ESM syntax.
46+
//
47+
// The createRequire banner provides a real `require` function in the ESM
48+
// scope. esbuild's __require shim (generated for CJS→ESM conversion)
49+
// checks `typeof require !== "undefined"` and uses it when available,
50+
// which fixes "Dynamic require of <builtin> is not supported" errors
51+
// from CJS dependencies like knex/tarn that require() Node.js built-ins.
52+
banner: {
53+
js: [
54+
'// Bundled by esbuild — see scripts/bundle-api.mjs',
55+
'import { createRequire } from "module";',
56+
'const require = createRequire(import.meta.url);',
57+
].join('\n'),
58+
},
59+
});
60+
61+
console.log('[bundle-api] Bundled server/index.ts → api/_handler.js');

0 commit comments

Comments
 (0)