First Steps
This page covers the concrete shape of a Slingshot app: file layout, the minimal config, and what runs at boot. If you want the conceptual map first, read Introduction. If you want to skip straight to a runnable app, jump to Quick Start.
Project structure
Section titled “Project structure”The framework requires exactly one file: app.config.ts. Everything else is convention.
Most apps end up looking something like this:
my-app/├── app.config.ts ← required — declares your app├── package.json└── src/ └── packages/ ├── blog/ │ ├── index.ts ← exports the blogPackage definition │ ├── post.entity.ts ← Post entity │ ├── post.operations.ts ← publish, archive, etc. │ ├── routes.ts ← custom domain routes (preview, feed) │ └── public.ts ← Package Contract (capabilities, public refs) └── notifications/ ├── index.ts └── public.tsThis is one common shape, not required. Each package owns its own entities, routes,
and public contract — they don’t live in top-level entities/ or routes/ folders. If
your project is tiny or you prefer a flat layout, the framework won’t stop you. The only
hard rule is: app.config.ts exists at the project root and exports defineApp({...}) as
default.
app.config.ts is the source of truth for what your app is: which packages it loads,
which database it talks to, what middleware runs, which port it binds.
The minimal app
Section titled “The minimal app”import { defineApp, definePackage, domain, route } from '@lastshotlabs/slingshot';
export default defineApp({ port: 3000, packages: [ definePackage({ name: 'app', domains: [ domain({ name: 'health', basePath: '/health', routes: [ route.get({ path: '/', summary: 'Health check', handler: ({ respond }) => respond.json({ ok: true }), }), ], }), ], }), ],});This is a complete app. It has one route, a typed handler, validation, and an OpenAPI spec.
Run it
Section titled “Run it”slingshot startThe framework discovers app.config.ts, imports its default export, and starts an HTTP
server. Three URLs are immediately available:
| URL | What |
|---|---|
http://localhost:3000/health | Your route |
http://localhost:3000/openapi.json | OpenAPI spec |
http://localhost:3000/docs | Interactive API docs |
What defineApp actually is
Section titled “What defineApp actually is”defineApp is a typed identity function — a single line of code:
export const defineApp = <T extends object>(config: AppConfig<T>): AppConfig<T> => config;It exists so your config object gets full autocomplete and type inference without you having to annotate it. The CLI loads the file, takes the default export, and hands it to the runtime.
That means:
- You can read your config back with
import config from './app.config'— it’s just an object - You can compose it programmatically — split it across files, derive parts conditionally
- You can test it without booting anything — just import it
What runs at boot
Section titled “What runs at boot”When slingshot start boots your config, the framework:
- Validates the config object
- Resolves secrets from the configured provider
- Connects databases and infrastructure
- Runs every plugin’s
setupMiddlewarein dependency order - Mounts package routes, generated entity routes, and
routesDirfiles - Registers OpenAPI specs and
/docs - Starts the HTTP server, plus WebSocket and SSE endpoints if configured
- Wires graceful shutdown for
SIGTERM/SIGINT
You get all of this without writing any of it.
What’s next
Section titled “What’s next”You have an app running. Now follow the canonical journey:
- Composing an App — the mental model for packages, custom routes, contracts, events, and plugins. This is the journey hub; every page below extends it.
- Packages — turn your app into self-contained feature modules.
- Authoring Routes — per-route concerns once you have routes worth tightening.
Reference for when you need it:
- App Config — every section of
defineApp, what it does, when to set it. - Entities — declare a data model, get CRUD for free.