@zfig/bootstrap
v0.9.15
Published
Server bootstrap helper for zfig configuration library
Readme
@zfig/bootstrap
Server lifecycle management with graceful shutdown for zfig.
Install
npm install zfig @zfig/bootstrapBasic Usage
Create vs Run
Create - returns server without starting:
import { schema, field } from "zfig";
import { bootstrap } from "@zfig/bootstrap";
import { z } from "zod";
const configSchema = schema({
port: field({ type: z.number(), env: "PORT", default: 3000 }),
});
const service = bootstrap(configSchema, (config) => {
// Return your server instance
return createServer(config);
});
// Get server without listening
const { server, config } = await service.create();Run - starts server with graceful shutdown:
// Starts listening and handles SIGTERM/SIGINT
await service.run();Autorun
Automatically run when file is executed directly (not imported).
ESM
import { schema, field } from "zfig";
import { bootstrap } from "@zfig/bootstrap";
import { z } from "zod";
import express from "express";
const configSchema = schema({
port: field({ type: z.number(), env: "PORT", default: 3000 }),
});
export default bootstrap(
configSchema,
{ autorun: { enabled: true, meta: import.meta } },
(config) => {
const app = express();
app.get("/health", (req, res) => res.send("ok"));
return app;
}
);
// node app.ts → auto-runs server
// import from app.ts → just exports serviceCommonJS
const { schema, field } = require("zfig");
const { bootstrap } = require("@zfig/bootstrap");
const { z } = require("zod");
const configSchema = schema({
port: field({ type: z.number(), env: "PORT", default: 3000 }),
});
module.exports = bootstrap(
configSchema,
{ autorun: { enabled: true, module: module } },
(config) => createServer(config)
);Run Options
await service.run({
port: 8080, // Override config port
host: "0.0.0.0", // Bind to specific host
onReady: () => { // Called after listen succeeds
console.log("Server ready");
},
onShutdown: () => { // Called after server closes
console.log("Server stopped");
},
configOverride: { // Override config values
debug: true,
},
});| Option | Type | Description |
|--------|------|-------------|
| port | number | Override port (default: from config or 3000) |
| host | string | Bind to specific host |
| onReady | () => void | Called after listen succeeds |
| onShutdown | () => void | Called after server closes |
| configOverride | object | Override config values |
Dynamic Run Options
Use a function to access resolved config:
export default bootstrap(
configSchema,
{
autorun: {
enabled: true,
meta: import.meta,
runOptions: (config) => ({
port: config.serverPort || 3000,
host: config.serverHost || "localhost",
}),
},
},
(config) => createServer(config)
);Or static options:
autorun: {
enabled: true,
meta: import.meta,
runOptions: { port: 3000, host: "localhost" },
}Graceful Shutdown
Bootstrap handles graceful shutdown automatically:
- Listens for
SIGTERMandSIGINTsignals - Calls
server.close()to stop accepting new connections - Waits for existing connections to complete
- Invokes
onShutdowncallback - Resolves the
run()promise
await service.run({
onShutdown: () => {
console.log("Cleanup complete");
},
});
// Promise resolves after shutdownTesting
Use create() to get the server without starting it:
import service from "./app";
import request from "supertest";
describe("app", () => {
it("responds to health check", async () => {
const { server } = await service.create({
override: { port: 0 },
});
const response = await request(server).get("/health");
expect(response.status).toBe(200);
});
});Create options:
await service.create({
configPath: "./test-config.json",
env: { PORT: "4000" },
override: { debug: true },
});API Reference
bootstrap(schema, factory)
Minimal signature without options.
const service = bootstrap(configSchema, (config) => server);bootstrap(schema, options, factory)
Full signature with startup options.
const service = bootstrap(
configSchema,
{
autorun: { enabled: true, meta: import.meta },
configPath: "./config.json",
onError: (error) => {
console.error("Startup failed:", error);
},
},
(config) => server
);Startup Options
| Option | Type | Description |
|--------|------|-------------|
| autorun | AutorunOptions | Auto-run configuration |
| configPath | string | Path to config file |
| env | Record<string, string> | Environment variables |
| secretsPath | string | Base path for secrets |
| initialValues | object | Base config values |
| override | object | Override all sources |
| onError | (error: Error) => void | Called on config or factory errors |
Error Handling
The onError callback receives all errors during startup. For config errors, diagnostics are attached:
import { ConfigError } from "zfig";
bootstrap(
configSchema,
{
onError: (error) => {
if (error instanceof ConfigError && error.diagnostics) {
console.error("Config resolution trace:", error.diagnostics);
}
},
},
(config) => server
);service.create(options?)
Returns server and config without starting.
const { server, config } = await service.create({
initialValues?: object,
configPath?: string,
env?: Record<string, string>,
secretsPath?: string,
override?: object,
});service.run(options?)
Starts server and handles graceful shutdown.
await service.run({
port?: number,
host?: string,
onReady?: () => void,
onShutdown?: () => void,
configOverride?: object,
});Examples
Express
import { schema, field } from "zfig";
import { bootstrap } from "@zfig/bootstrap";
import { z } from "zod";
import express from "express";
const configSchema = schema({
port: field({ type: z.number(), env: "PORT", default: 3000 }),
name: field({ type: z.string(), default: "my-app" }),
});
export default bootstrap(
configSchema,
{ autorun: { enabled: true, meta: import.meta } },
(config) => {
const app = express();
app.get("/", (req, res) => res.json({ name: config.name }));
return app;
}
);Fastify
import { schema, field } from "zfig";
import { bootstrap } from "@zfig/bootstrap";
import { z } from "zod";
import Fastify from "fastify";
const configSchema = schema({
port: field({ type: z.number(), env: "PORT", default: 3000 }),
});
export default bootstrap(
configSchema,
{ autorun: { enabled: true, meta: import.meta } },
(config) => {
const app = Fastify();
app.get("/health", async () => ({ status: "ok" }));
return app;
}
);Fastify's Promise-based listen() is supported automatically.
License
MIT
