Routes and Handlers
Slingshot gives you three ways to add routes. They are not alternatives; they layer.
| Use this | When |
|---|---|
| Generated routes | An entity has standard CRUD or operation routes |
| Package routes | A feature has custom HTTP handlers that belong to a domain |
routesDir | Top-level routes that don’t fit a package, or quick experiments |
This page covers the second and third. Generated entity routes are covered under Data and Entities.
Package routes
Section titled “Package routes”Package routes live inside definePackage. They are typed, schema-validated, and show up
in OpenAPI alongside generated routes.
import { z } from 'zod';import { definePackage, domain, route } from '@lastshotlabs/slingshot';
export const blogPackage = definePackage({ name: 'blog', domains: [ domain({ name: 'posts', basePath: '/posts', routes: [ route.get({ path: '/featured', summary: 'List featured posts', responses: { 200: { description: 'List of featured posts', schema: z.object({ items: z.array(z.object({ id: z.string(), title: z.string() })) }), }, }, handler: ({ respond }) => respond.json({ items: [] }), }), route.post({ path: '/:id/like', summary: 'Like a post', auth: 'userAuth', request: { params: z.object({ id: z.string() }) }, handler: ({ params, respond }) => respond.json({ liked: params.id }), }), ], }), ],});Wire the package into your config:
import { defineApp } from '@lastshotlabs/slingshot';import { blogPackage } from './src/blog/package';
export default defineApp({ port: 3000, packages: [blogPackage],});Each route.* call returns a typed route definition. The handler signature is fully
inferred from the schemas — params, body, and the response shape are all checked at
compile time.
File-system routing via routesDir
Section titled “File-system routing via routesDir”For small apps, prototypes, or top-level routes that don’t belong to any domain, set
routesDir and drop files in:
import { defineApp } from '@lastshotlabs/slingshot';
export default defineApp({ port: 3000, routesDir: './src/routes',});import { Hono } from 'hono';
const router = new Hono();
router.get('/orders', async c => { return c.json({ orders: [] });});
router.post('/orders', async c => { const body = await c.req.json(); return c.json({ id: 'ord_123' }, 201);});
export { router };Slingshot discovers every file under routesDir that exports a named router. File names
don’t drive routing — paths come from what you register on the Hono instance. Nest files
freely.
Inside a handler
Section titled “Inside a handler”Whether you write package routes or routesDir handlers, the request model is the same
underneath. Hono is the foundation; Slingshot adds typed input parsing and the runtime
context.
// @skip-typecheckimport { getActor, getContext, getRequestTenantId, route } from '@lastshotlabs/slingshot';
route.get({ path: '/me', auth: 'userAuth', handler: async ({ c, respond }) => { const ctx = getContext(c); // SlingshotContext for this app const actor = getActor(c); // resolved user / m2m / anon const tenantId = getRequestTenantId(c); // multi-tenant scope return respond.json({ actor, tenantId }); },});c is the raw Hono context — every Hono API works. respond.json(...), respond.text(...),
and respond.error(...) are typed helpers that pair with the route’s response schema.
Route configuration cheat sheet
Section titled “Route configuration cheat sheet”// @skip-typecheckimport { z } from 'zod';import { route } from '@lastshotlabs/slingshot';
route.post({ path: '/items', summary: 'Create an item', description: 'Longer description for OpenAPI docs', tags: ['items'], auth: 'userAuth', // 'userAuth' | 'none' | array of guards request: { body: z.object({ name: z.string() }) }, responses: { 201: { description: 'Created', schema: z.object({ id: z.string() }) }, 409: { description: 'Conflict', schema: z.object({ error: z.string() }) }, }, middleware: ['rateLimit'], // names from package middleware map handler: async ({ body, respond }) => { return respond.json({ id: '...' }, 201); },});The same surface works for route.get, route.put, route.patch, route.delete. See
Domain and Route for the full reference.
What’s next
Section titled “What’s next”- Middleware — request-level interceptors
- Context and Request Model —
getContext, actor, tenant - OpenAPI and Validation — schema-first specs
- Domain and Route — the full route DSL reference