Skip to content

App Roots and Runtime

Every Slingshot app starts at the app root. This is where you assemble your packages, set config, and start the server.

If you have not built anything yet, start with the Quick Start. Come back here when you want to understand what defineApp(...) actually does.

app.config.ts
// @skip-typecheck
import { defineApp } from '@lastshotlabs/slingshot';
import { createAuthPlugin } from '@lastshotlabs/slingshot-auth';
import { adminPackage } from './src/packages/admin';
import { blogPackage } from './src/packages/blog';
export default defineApp({
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, adminPackage],
});

This one file controls:

  • What runs — which packages and plugins to load
  • How it connects — database, secrets, ports
  • How it is secured — auth, CORS, rate limits
  • What it supports — events, WebSockets, SSE, jobs

The app root stays small. Feature logic lives in packages. Boot it with slingshot start.

When slingshot start loads your app.config.ts, the framework:

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

You get all of this from one function call.

FunctionUse when
defineApp()Authoring app.config.ts — the canonical path
createServer()Custom imperative entry points (rare)
createApp()Testing, custom hosting, or programmatic use

defineApp() is a typed identity helper for the config object. The CLI runner picks up the default export and calls createServer() for you.

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

// @skip-typecheck
const { app, ctx } = await createApp({ packages: [blogPackage] });
const response = await app.request('/posts');
await ctx.destroy();

The top-level config covers everything your app needs:

Config areaWhat it controls
portServer port
securityJWT signing, CORS, CSRF, rate limits, helmet
dbDatabase connections (Postgres, SQLite, MongoDB, etc)
packagesFeature packages to load
pluginsRaw plugins (auth, permissions, etc)
wsWebSocket endpoints
eventBusEvent bus adapter (in-process, BullMQ, Redis)
secretsSecret provider (env, vault, etc)
jobsOrchestration adapter
multiTenancyTenant resolver and isolation settings

See App Config for the full reference.

You understand the app root and runtime layer. The canonical journey continues into per-route concerns:

  1. Authoring Routes — every concern that applies to a single route: validation, DTO mapping, idempotency, exception handling.
  2. Working with Data — when a route should be generated from an entity model.
  3. Security — auth and permissions.

Adjacent reference: