@constela/start
Full-stack meta-framework for Constela applications with file-based routing, SSR, and edge runtime support
Note
@constela/start is the meta-framework for building full-stack Constela applications, similar to what SolidStart is for SolidJS.
Overview
@constela/start provides everything you need to build production-ready Constela applications:
- File-based routing: Routes are automatically generated from the
src/routes/directory structure - SSR: Server-side rendering using
@constela/server'srenderToString - SSG: Static site generation with
getStaticPathssupport - API Routes: Full support for GET/POST/PUT/DELETE/PATCH endpoints
- Middleware: Koa/Hono-style
next()pattern for request handling - Edge Runtime: Deploy to Cloudflare Workers, Vercel Edge, or Deno
- Vite-based: Development server with HMR support
Constela Ecosystem
| SolidJS | Constela | Purpose |
|---|---|---|
| solid-js | @constela/core | Core type definitions |
| solid-js/web | @constela/runtime | Client-side renderer |
| solid-js/server | @constela/server | SSR renderer |
| SolidStart | @constela/start | Meta-framework |
Installation
npm install @constela/startOr create a new project:
npx create-constela my-app
cd my-app
npm install
npm run devProject Structure
A typical @constela/start project has the following structure:
my-app/
├── src/
│ ├── routes/
│ │ ├── index.json # /
│ │ ├── about.json # /about
│ │ ├── users/
│ │ │ ├── index.json # /users
│ │ │ └── [id].json # /users/:id
│ │ └── api/
│ │ └── users.ts # /api/users (API route)
│ └── layouts/
│ └── main.json # Layout definition
├── constela.config.ts
└── package.jsonFile-based Routing
Routes are determined by the file structure inside src/routes/:
| File Path | Route |
|---|---|
src/routes/index.json | / |
src/routes/about.json | /about |
src/routes/users/index.json | /users |
src/routes/users/[id].json | /users/:id |
src/routes/posts/[...slug].json | /posts/* (catch-all) |
Page Definition
Each page is a JSON file with the Constela DSL:
{
"version": "1.0",
"route": {
"path": "/",
"layout": "main",
"meta": {
"title": "Home",
"description": "Welcome to my app"
}
},
"state": {
"count": { "type": "number", "initial": 0 }
},
"actions": [
{
"name": "increment",
"steps": [
{ "do": "update", "target": "count", "operation": "increment" }
]
}
],
"view": {
"kind": "element",
"tag": "div",
"children": [
{ "kind": "text", "value": { "expr": "state", "name": "count" } },
{
"kind": "element",
"tag": "button",
"props": { "onClick": { "event": "click", "action": "increment" } },
"children": [
{ "kind": "text", "value": { "expr": "lit", "value": "+1" } }
]
}
]
}
}Dynamic Routes
Use square brackets for dynamic segments:
{
"version": "1.0",
"route": {
"path": "/users/:id"
},
"view": {
"kind": "element",
"tag": "div",
"children": [
{ "kind": "text", "value": { "expr": "lit", "value": "User ID: " } },
{ "kind": "text", "value": { "expr": "route", "name": "id" } }
]
}
}Catch-all Routes
Use [...slug] for catch-all routes:
src/routes/docs/[...slug].json
# Matches /docs/a, /docs/a/b, /docs/a/b/c, etc.SSR & Hydration
@constela/start provides server-side rendering out of the box. Pages defined in JSON are automatically SSR'd on the server and hydrated on the client for optimal performance and SEO.
Tip
No additional configuration is required for SSR. Just create your JSON route files and the framework handles the rest.
SSG (Static Site Generation)
For static pages with dynamic routes, use the getStaticPaths field:
{
"version": "1.0",
"route": {
"path": "/posts/:slug",
"getStaticPaths": {
"source": "posts",
"params": {
"slug": {
"expr": "get",
"base": { "expr": "var", "name": "item" },
"path": "slug"
}
}
}
},
"data": {
"posts": {
"type": "glob",
"pattern": "content/posts/*.mdx",
"transform": "mdx"
}
},
"view": { ... }
}Build Output
npx constela-start build
# Generates:
# dist/posts/hello-world.html
# dist/posts/another-post.html
# ...API Routes
Create API endpoints by creating TypeScript files with HTTP method handlers:
// src/routes/api/users.ts
import type { APIContext } from '@constela/start';
export async function GET({ params, query }: APIContext) {
const users = await db.users.findMany();
return Response.json({ users });
}
export async function POST({ request }: APIContext) {
const body = await request.json();
const user = await db.users.create(body);
return Response.json({ created: user }, { status: 201 });
}
export async function PUT({ request, params }: APIContext) {
const body = await request.json();
const user = await db.users.update(params.id, body);
return Response.json({ updated: user });
}
export async function DELETE({ params }: APIContext) {
await db.users.delete(params.id);
return new Response(null, { status: 204 });
}Note
API routes require TypeScript because they contain server-side logic that cannot be expressed in JSON.
APIContext
| Property | Type | Description |
|---|---|---|
request | Request | The incoming request object |
params | Record<string, string> | Route parameters |
query | URLSearchParams | Query string parameters |
env | object | Environment bindings (for edge runtimes) |
Middleware
Create middleware using the next() pattern:
// src/middleware.ts
import type { MiddlewareHandler } from '@constela/start';
export const middleware: MiddlewareHandler = async (ctx, next) => {
// Before request
console.log(`${ctx.request.method} ${ctx.request.url}`);
const start = Date.now();
// Call next middleware or route handler
const response = await next();
// After request
console.log(`Completed in ${Date.now() - start}ms`);
return response;
};Route-specific Middleware
// src/routes/admin/_middleware.ts
import type { MiddlewareHandler } from '@constela/start';
export const middleware: MiddlewareHandler = async (ctx, next) => {
const token = ctx.request.headers.get('Authorization');
if (!isValidToken(token)) {
return new Response('Unauthorized', { status: 401 });
}
return next();
};Note
Middleware files prefixed with _ (e.g., _middleware.ts) apply to all routes in that directory and its subdirectories.
CLI Commands
Development Server
npx constela-start dev| Option | Description |
|---|---|
--port, -p | Port number (default: 3000) |
--host | Host to bind (default: localhost) |
--open | Open browser automatically |
Production Build
npx constela-start build| Option | Description |
|---|---|
--target | Build target: node, cloudflare, vercel, deno |
--outDir | Output directory (default: dist) |
Production Server
npx constela-start startTip
For serverless deployments, the build output is self-contained and ready to deploy.
Edge Runtime
@constela/start supports deployment to edge platforms:
Cloudflare Workers
// constela.config.ts
import { defineConfig } from '@constela/start';
export default defineConfig({
target: 'cloudflare',
// Cloudflare-specific options
});Vercel Edge
// constela.config.ts
import { defineConfig } from '@constela/start';
export default defineConfig({
target: 'vercel',
edge: true
});Deno Deploy
// constela.config.ts
import { defineConfig } from '@constela/start';
export default defineConfig({
target: 'deno'
});Configuration Options
| Option | Type | Description |
|---|---|---|
target | 'node' | 'cloudflare' | 'vercel' | 'deno' | Deployment target |
edge | boolean | Enable edge runtime (Vercel) |
ssr | boolean | Enable server-side rendering (default: true) |
prerender | string[] | Routes to pre-render at build time |
Summary
@constela/start provides:
- Zero-config development: Just create JSON routes and start building
- File-based routing: Intuitive directory-to-URL mapping
- Full-stack capabilities: SSR, SSG, and API routes in one framework
- Edge-ready: Deploy anywhere with platform-specific adapters
- JSON-first pages: Define pages in Constela JSON DSL, use TypeScript only for server logic