Routing System

File Conventions

Master TanStack Router-style file conventions for REST APIs. Learn flat routes, nested folders, path params ($id), catch-alls ($), and layouts.

Taser adopts modern TanStack Router-style file routing conventions tailored specifically for REST APIs. Your filesystem serves as the single source of truth for URL endpoints, HTTP methods, and cascading middleware pipelines.


Route Files vs Layout Files

Under your src/routes/ directory, files are classified into two distinct types:

  1. Route Files: Filenames containing an HTTP verb before .ts (such as users.get.ts, posts.post.ts, items.$id.delete.ts, tasks/$id.complete.patch.ts). Route files export a default route definition created with t.get(), t.post(), etc.
  2. Layout Files: Filenames ending in .ts without an HTTP verb (such as $.ts, admin.ts, _auth.ts, tasks/$id.ts). Layout files export a default middleware pipeline created with t.layout().

HTTP Method Suffixes

Taser determines the HTTP verb from the final extension segment before .ts:

File NameHTTP MethodEndpoint URL
src/routes/users.get.tsGET/users
src/routes/users.post.tsPOST/users
src/routes/users.put.tsPUT/users
src/routes/users.patch.tsPATCH/users
src/routes/users.delete.tsDELETE/users
src/routes/users.options.tsOPTIONS/users
src/routes/users.head.tsHEAD/users
src/routes/users.query.tsQUERY/users
src/routes/users.any.tsANY (multi-method)/users
src/routes/users.all.tsALL (catch-all verb)/users

Flat Routes, Directory Routes, and Mixed Notation

Taser supports nested directory structures, flat dot notation, and mixed folder + dot notation. You can use whichever structure keeps your codebase cleanest:

GETindex.get.ts
/posts
GET$id.get.ts
/posts/:id
PUTedit.put.ts
/posts/:id/edit
PATCHcomplete.patch.ts
/tasks/:id/complete

Dot Separators in Filenames

A dot . inside a filename (e.g. tasks/$id.complete.patch.ts or posts.$id.edit.get.ts) acts as a URL segment separator (/), allowing you to define sub-routes flatly without creating deeply nested folders.


Index Routes (index.<method>.ts)

An index segment targets the root of its parent route without appending /index to the public URL:

GETindex.get.ts
/
GETapi.index.get.ts
/api
GETindex.get.ts
/products
POSTindex.post.ts
/products
GETposts.$id.index.get.ts
/posts/:id

Dynamic Path Parameters ($param)

Prefixing a segment with $ denotes a named URL path parameter:

GETusers.$id.get.ts
/users/:id
PATCH$id.complete.patch.ts
/tasks/:id/complete
GETorgs.$orgId.repos.$repoId.get.ts
/orgs/:orgId/repos/:repoId

Inside your route handler, parameters are automatically typed and validated on ctx.params:

src/routes/tasks/$id.complete.patch.ts
import { json } from "@taserjs/router/reply";
import { z } from "zod";
import { t } from "@taserjs/router";

export default t
  .patch("/tasks/:id/complete")
  .params(z.object({ id: z.string().uuid() }))
  .handler((ctx) => {
    // ctx.params.id is strictly typed as a UUID string
    return json({ taskId: ctx.params.id, completed: true });
  });

Wildcard Catch-All Splats ($)

A standalone $ segment captures wildcard rest segments (splats):

GET$.get.ts
/*
GETfiles.$.get.ts
/files/*
GET$.get.ts
/files/*

In your handler, the wildcard path matches all trailing sub-paths.


Root Layout Middleware ($.ts)

A top-level file named src/routes/$.ts serves as the Root Layout Middleware. It runs before every route in your application:

src/routes/$.ts
import { bodyLimit } from "@taserjs/router/body-limit";
import { secureHeaders } from "@taserjs/router/secure-headers";
import { t } from "@taserjs/router";

export default t
  .layout("/*")
  .use(secureHeaders())
  .use(bodyLimit({ maxSize: 1_000_000 }));

Pathless Layouts (Leading Underscore _layout)

A segment starting with an underscore _ (like _auth.ts, _auth/, or _auth.login.post.ts) is pathless. It applies scoped middleware to child routes without adding any segment to the public URL:

LAYOUT_auth.ts
/* (scoped _auth)
GETdashboard.get.ts
/dashboard
GETprofile.get.ts
/profile
POSTlogin.post.ts
/login

In flat dot notation:

GET_auth.dashboard.get.ts
/dashboard
GET_auth.profile.get.ts
/profile
GET_app._dashboard.metrics.get.ts
/metrics

Notice that _auth, _app, and _dashboard are omitted from the public URL.


Layout Breakout Routes (Trailing Underscore segment_)

A segment ending with an underscore _ is a breakout route (un-nested route). It targets the expected URL path, but breaks out of the parent layout hierarchy so it skips that segment's layout middleware.

The Problem Breakout Routes Solve

Suppose you have an authenticated /posts layout in src/routes/posts.ts (or a /tasks/:id layout in src/routes/tasks/$id.ts) that enforces user login or permission checks. You want a specific child endpoint (like /posts/:id/preview or /tasks/:id/complete) to skip that layout's checks.

By adding a trailing underscore to posts_ or $id_, the route keeps the URL segment but skips the layout:

ROOT$.ts
/*
LAYOUTposts.ts
/posts/*
GETindex.get.ts
/posts
GET$id.get.ts
/posts/:id
BREAKOUTposts_.$id.preview.get.ts
/posts/:id/preview (skips posts.ts)
LAYOUT$id.ts
/tasks/:id/*
GET$id.status.get.ts
/tasks/:id/status
BREAKOUT$id_.complete.patch.ts
/tasks/:id/complete (skips $id.ts)

Underscore Convention Rules

  • Leading underscore (_auth): Pathless layout / group (adds middleware, removes segment from URL). - Trailing underscore (posts_ or $id_): Breakout route (keeps segment in URL, removes parent layout from middleware chain).

Escaping Special Characters ([...])

When you need a literal character that would otherwise trigger a router convention (such as a literal dot ., literal leading underscore _, or literal index), wrap the character in brackets [...]:

File NameResolved URLExplanation
src/routes/sitemap[.]xml.get.tsGET /sitemap.xmlEscaped dot [.] prevents splitting into /sitemap/xml
src/routes/docs/v1[.]0/api.get.tsGET /docs/v1.0/apiPreserves v1.0 as a single path segment
src/routes/[_]private.get.tsGET /_privateEscaped [_] prevents segment from being treated as a pathless layout
src/routes/tasks/task[_].get.tsGET /tasks/task_Escaped [_] prevents segment from being treated as a layout breakout
src/routes/items/[index].get.tsGET /items/indexEscaped [index] prevents trimming to /items

Ignored & Private Files (- Prefix)

To co-locate utility files, helper functions, schemas, or test fixtures directly alongside route files without generating routes, prefix the file or folder with a dash -:

IGNORED-types.ts
(not routed)
IGNORED-schema.ts
(not routed)
GETindex.get.ts
/users
GET$id.get.ts
/users/:id
IGNOREDutils.ts
(not routed)

Complete Conventions Reference

File Path in src/routes/HTTP MethodResolved API URLLayout InheritanceDescription
$.tsN/AGlobalRootRoot application layout
index.get.tsGET//*Root index route
posts.tsN/A/posts/*Scoped layout for posts
posts.index.get.tsGET/posts/*/postsFlat posts index
posts.$id.get.tsGET/posts/:id/*/postsFlat parameterized route
tasks/$id.complete.patch.tsPATCH/tasks/:id/complete/*/tasksMixed folder + dot nested route
posts_.$id.edit.get.tsGET/posts/:id/edit/*Breakout route: skips posts.ts layout
tasks/$id_.complete.patch.tsPATCH/tasks/:id/complete/*/tasksBreakout route: skips tasks/$id.ts layout
files.$.get.tsGET/files/*/*Wildcard splat handler
_auth.tsN/AScoped/*Pathless auth layout
_auth.settings.get.tsGET/settings/*/_authPathless scoped route
sitemap[.]xml.get.tsGET/sitemap.xml/*Bracket-escaped literal dot
-helpers.tsN/AN/AN/AIgnored file

Next Steps