@luckystack/server
v0.2.6
Published
One-call server bootstrap for LuckyStack: HTTP + Socket.io + framework routes (api, sync, _health, /auth, uploads) wired together. Consumer's server.ts shrinks to ~20 lines.
Maintainers
Readme
@luckystack/server
One-call server bootstrap for LuckyStack. HTTP + Socket.io + framework routes (
/api/*,/sync/*,/_health,/_test/reset,/auth/*,/uploads/*) wired together. Yourserver.tsshrinks to ~20 lines.
Install
npm install @luckystack/server @luckystack/core @luckystack/api @luckystack/login @luckystack/sync @luckystack/presence socket.ioQuickstart — recommended (bootstrapLuckyStack)
bootstrapLuckyStack is a thin wrapper around createLuckyStackServer that auto-imports your luckystack/<pkg>/*.ts overlay files in topological order before listening, so registries (DI, hooks, providers) are populated by the time the HTTP server boots. This is what create-luckystack-app scaffolds.
import 'dotenv/config';
import '../config'; // your registerProjectConfig(...)
import '../deploy.config'; // your registerDeployConfig(...)
import '../services.config'; // your registerServicesConfig(...)
import { bootstrapLuckyStack } from '@luckystack/server';
import { serveFile, serveFavicon } from './prod/serveFile';
const server = await bootstrapLuckyStack({
serveFile,
serveFavicon,
});
await server.listen();The bootstrap call runs (in order):
- Auto-imports
luckystack/core/*.ts,luckystack/deploy/*.ts,luckystack/login/*.ts,luckystack/sentry/*.ts,luckystack/presence/*.ts,luckystack/docs-ui/*.ts,luckystack/server/*.ts(topologically sorted, then alphabetically inside each folder). - Hands off to
createLuckyStackServer(options).
Set skipOverlayLoad: true to handle every registration yourself, or overlayRoot: 'custom-folder' to load from somewhere other than ./luckystack.
Pre-flight check — verifyBootstrap
Call verifyBootstrap after the overlay loads (or after your manual registrations) and before server.listen() if you want to fail fast on missing registrations instead of silent runtime crashes:
import { bootstrapLuckyStack, verifyBootstrap } from '@luckystack/server';
const server = await bootstrapLuckyStack({ serveFile, serveFavicon });
await verifyBootstrap({
requireDeployConfig: true, // single-instance deploys can omit this
requireServicesConfig: true, // only true when the router will run
requireOAuthProviders: true, // false if you only use credentials login
});
await server.listen();verifyBootstrap throws a single descriptive Error listing every missing registration. The full check covers:
ProjectConfig— always required.DeployConfig— whenrequireDeployConfig: true.ServicesConfig— whenrequireServicesConfig: true.- OAuth providers — when
requireOAuthProviders: trueand only the defaultcredentialsentry has been registered. RuntimeMapsProvider— hard-fails in production; loud-warns in dev because devkit hot-reload normally registers one. Without it, every API/sync request silently resolves tonotFound.LocalizedNormalizer— hard-fails in production; warns in dev. Without it, error responses degrade toerrorCode-as-message (no i18n).
Lower-level entry — createLuckyStackServer
Use this directly when you want to control the order of imports yourself or skip the overlay convention:
import { initializeSentry } from '@luckystack/error-tracking';
import { registerPresenceHooks } from '@luckystack/presence';
import { createLuckyStackServer } from '@luckystack/server';
initializeSentry();
registerPresenceHooks();
const server = await createLuckyStackServer({
serveFile,
serveFavicon,
});
await server.listen();Both entries handle devkit hot reload + console init in dev mode automatically — opt out with enableDevTools: false if you have your own.
Options
interface CreateLuckyStackServerOptions {
port?: number | string; // default: process.env.SERVER_PORT ?? 80
ip?: string; // default: process.env.SERVER_IP ?? '127.0.0.1'
serveFile?: StaticFileHandler; // catch-all for non-framework GETs (Vite output, SPA fallback)
serveFavicon?: FaviconHandler; // /favicon.ico
customRoutes?: CustomRouteHandler; // pre-fallback hook; return true to mark handled
enableDevTools?: boolean; // default: NODE_ENV !== 'production'
maxHttpBufferSize?: number; // default: 5 MB
}customRoutes runs before the static file serving, so you can add project-specific HTTP endpoints (webhooks, OG image generation, etc.) without forking the package. Return true if you ended the response.
You can also register custom routes globally via registerCustomRoute(handler) — the bootstrap call composes every registered handler into the running server. This is what @luckystack/docs-ui uses to mount /_docs.
import { registerCustomRoute } from '@luckystack/server';
registerCustomRoute(async (req, res) => {
if (req.url !== '/healthz') return false;
res.statusCode = 200;
res.end('ok');
return true;
});What it wires
- HTTP server with CORS + security headers, OPTIONS preflight, method validation, cookie sliding, CSRF middleware.
- Socket.io server attached to the HTTP server, with optional Redis adapter when configured in deploy config.
- Framework routes:
/_health,/livez,/readyz,/_test/reset(dev/test only),/api/*and/sync/*(with SSE streaming),/auth/api,/auth/callback/*,/uploads/*,/assets/*,/csrf-token. - Presence broadcasting (when enabled in project config): connect / disconnect / reconnect, location updates, peer notifications.
- Boot UUID written on startup so the load-balancer (
@luckystack/router) can detect rolling restarts. - Dev tools in non-production: devkit hot reload, console initializer.
HTTP route handler layout
handleHttpRequest is now a thin (~190-line) orchestrator that sets up CORS / origin / security headers, runs the CSRF middleware, then dispatches a route-handler table. Each handler matches its own route path and returns boolean (handled or not). The handlers live in packages/server/src/httpRoutes/:
| File | Routes |
| --- | --- |
| csrfMiddleware.ts + csrfRoute.ts | CSRF guard on writes + GET /csrf-token |
| healthRoutes.ts | /livez, /readyz, /_health |
| testResetRoute.ts | /_test/reset (dev/test only — see Security below) |
| faviconRoute.ts | /favicon.ico |
| uploadsRoute.ts | /uploads/* (avatar serving via serveAvatar) |
| authApiRoute.ts | /auth/api/* |
| authCallbackRoute.ts | /auth/callback/* |
| apiRoute.ts | /api/* (delegates to @luckystack/api's handleHttpApiRequest) |
| syncRoute.ts | /sync/* (delegates to @luckystack/sync's handleHttpSyncRequest) |
| customRoutes.ts | Calls every handler registered via registerCustomRoute(...) and the inline customRoutes option |
| staticRoutes.ts | Final fallback to the consumer's serveFile(...) (Vite SPA, etc.) |
Top-level handleHttpRequest + dispatchRoutes(handlers, ctx) are the only orchestration; everything else is a flat list of single-purpose handlers. SSE handling, error fall-through, and dispatch order are preserved.
Security defaults that may surprise you
- CORS fail-closed. If neither
OriginnorRefereris present, only read-only methods (GET, HEAD, OPTIONS) are allowed; state-changing methods are rejected with 403. Earlier builds fell back toHost, which silently bypassed CORS for non-browser callers (curl, server-to-server). When youcurla write endpoint, set-H 'Origin: https://your-allowed-origin'. /_test/resetis fail-closed. It requires bothNODE_ENVto be exactlydevelopmentortestAND a non-emptyTEST_RESET_TOKENenv var. Any other state returns 403 (no silent allow-list). WireTEST_RESET_TOKENin your dev/test.env.localand pass it as thex-test-reset-token: ${TOKEN}header on the reset call.@luckystack/test-runner'sresetServerStatereads the same env var.- CSRF middleware. Writes to
/api/*and/sync/*require anx-csrf-tokenheader that matches the value minted on the session record (mirrored viaGET /auth/csrf). TheapiRequesthelper in@luckystack/core/clientattaches it automatically. Rejections dispatch thecsrfMismatchhook before returning 403; the payload contains presence-only token info, never the value. The header name, token length, and cookie options are customisable viaregisterCsrfConfig({ headerName, tokenLength, cookieOptions })from@luckystack/core— seepackages/core/docs/csrf-config.md.
Public API
| Export | Purpose |
| --- | --- |
| bootstrapLuckyStack(options) | High-level entry: verifies config, auto-imports overlay, then calls createLuckyStackServer. |
| createLuckyStackServer(options) | Lower-level factory that returns { httpServer, ioServer, listen }. |
| verifyBootstrap(requirements?) | Pre-flight check for project/deploy/services config and required env keys. |
| registerCustomRoute(handler) / getCustomRoutes() / clearCustomRoutes() | Global custom-route registry composed by bootstrapLuckyStack. |
| Hook payload types: OnSocketConnectPayload, OnSocketDisconnectPayload, PreRoomJoinPayload, PostRoomJoinPayload, PreRoomLeavePayload, PostRoomLeavePayload, OnLocationUpdatePayload | For socket-lifecycle hook handlers. |
| Types: CreateLuckyStackServerOptions, BootstrapLuckyStackOptions, BootstrapRequirements, RunningLuckyStackServer, RouteContext, StaticFileHandler, FaviconHandler, CustomRouteHandler | Handler typing. |
Dependencies
- Runtime:
@luckystack/api,@luckystack/core,@luckystack/login,@luckystack/presence,@luckystack/sync - Peer (canonical ranges, standardized 2026-05-07):
@prisma/client@^6.19.0(transitively required via@luckystack/core)socket.io@^4.8.0
- Optional peer:
@luckystack/error-tracking,@luckystack/email,@luckystack/docs-ui— bootstrap auto-detects them but does not require them.
Selecting bundles + port at runtime
@luckystack/server/parseArgv is a side-effect-only entrypoint. Import it as the first line of your server.ts so positional CLI args are parsed before any module that reads process.env.SERVER_PORT at load time (notably your project's config.ts).
// server.ts
import '@luckystack/server/parseArgv';
// ...rest of your bootstrapArgv shape: <bundle[,bundle...]> [port]
npm run server # default preset, port 80
npm run server -- billing # one bundle, port 80
npm run server -- billing,vehicles 4001 # merge bundles, listen on 4001When multiple presets are passed, the framework loads each preset's generated route map and shallow-merges apis / syncs / functions. Key collisions across presets throw at boot (services must own exactly one preset).
Related architecture docs
docs/ARCHITECTURE_PACKAGING.md— package split, multi-service builds, preset bundles.docs/ARCHITECTURE_SOCKET.md— Socket.io setup, Redis adapter, room model.docs/HOSTING.md— multi-instance deployment,@luckystack/router, health probes.
License
MIT — see LICENSE.
