Skip to content

createServer and createApp

Every Slingshot app starts with one of two functions: createServer() for production, or createApp() for tests and custom hosting.

createServer — the production entrypoint

Section titled “createServer — the production entrypoint”
src/index.ts
// @skip-typecheck
import { createServer } from '@lastshotlabs/slingshot';
import { createAuthPlugin } from '@lastshotlabs/slingshot-auth';
import { blogPackage } from './packages/blog';
await createServer({
port: 3000,
security: {
signing: { secret: process.env.JWT_SECRET! },
cors: { origin: 'https://myapp.com' },
},
db: {
default: { adapter: 'postgres', url: process.env.DATABASE_URL! },
},
plugins: [createAuthPlugin({ auth: { roles: ['user', 'admin'], defaultRole: 'user' } })],
packages: [blogPackage],
});

createServer() does everything:

  1. Validates your config
  2. Resolves secrets
  3. Connects databases
  4. Boots plugins and packages in dependency order
  5. Registers middleware, routes, and OpenAPI docs
  6. Starts the HTTP server
  7. Starts WebSocket and SSE transports
  8. Wires graceful shutdown

Run it:

Terminal window
bun run src/index.ts

Your API is live at http://localhost:3000, docs at http://localhost:3000/docs.

createApp() does everything except start the network server. Use it in tests:

tests/blog.test.ts
// @skip-typecheck
import { beforeEach, describe, expect, test } from 'bun:test';
import { createApp, getContext } from '@lastshotlabs/slingshot';
import { blogPackage } from '../src/packages/blog';
describe('blog', () => {
let app: Awaited<ReturnType<typeof createApp>>['app'];
let ctx: ReturnType<typeof getContext>;
beforeEach(async () => {
const result = await createApp({
packages: [blogPackage],
});
app = result.app;
ctx = getContext(result.app);
});
test('GET /posts returns empty list', async () => {
const res = await app.request('/posts');
expect(res.status).toBe(200);
const body = (await res.json()) as { items: unknown[] };
expect(body.items).toHaveLength(0);
});
test('POST /posts creates a post', async () => {
const res = await app.request('/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'Hello', body: 'World' }),
});
expect(res.status).toBe(201);
});
});

No port binding, no network overhead. app.request() calls your routes in-process.

Returns a promise that resolves when the server is running. The server process stays alive until shutdown (SIGTERM, SIGINT, or ctx.destroy()).

Returns { app, ctx }:

PropertyWhat it is
appThe assembled Hono app — call app.request() for tests
ctxThe SlingshotContext — events, persistence, cleanup

Use ctx for cleanup:

// Reset in-memory state between tests
await ctx.clear();
// Tear down connections when done
await ctx.destroy();
ScenarioUse
Running your app in productioncreateServer()
Running your app in devcreateServer()
Unit and integration testscreateApp()
Custom server (e.g., Lambda)createApp()
Embedding in another frameworkcreateApp()
await createServer({
port: 3000,
packages: [appPackage],
});

No db config means in-memory storage. Data resets on restart.

// @skip-typecheck
await createServer({
port: Number(process.env.PORT ?? 3000),
security: {
signing: { secret: process.env.JWT_SECRET! },
cors: { origin: process.env.CORS_ORIGIN! },
csrf: { enabled: true },
},
db: {
default: { adapter: 'postgres', url: process.env.DATABASE_URL! },
},
logging: { enabled: true },
metrics: { enabled: true },
plugins: [createAuthPlugin({ auth: { roles: ['user', 'admin'], defaultRole: 'user' } })],
packages: [blogPackage, adminPackage],
});