Development Guide

Folder Structure

Folder structure, file naming conventions, and how Legonode maps files to routes, middleware, events, and schedules.

Folder Structure

This guide explains how Legonode organizes your appDir directory: which folders and file names are special, how URL paths are derived, and how to name files for routes, middleware, events, and scheduled tasks.


App folder structure

All application code that Legonode loads lives under a single appDir (by default ./src, configurable via legonode.config.ts). The layout looks like this:

legonode.config.ts
package.json
  • Routes — Route files live under router/; the folder path under router/ becomes the URL path.
  • Middlewarerouter/**/middleware.ts (or .js/.mts/.mjs) applies to that directory and all subpaths (prefix is relative to router/).
  • Events — Only files inside appDir/events/ are loaded as event handlers.
  • Schedules — Only files inside appDir/cron/ are loaded as scheduled (cron) tasks.

Route file naming

Legonode treats two kinds of files as route handlers:

1. Single file for all methods: route.ts

  • Names: route.ts, route.js, route.mts, route.mjs
  • Meaning: This path handles every HTTP method you implement (e.g. GET, POST).
  • Exports: Export a function per method: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, and/or default (used when the request method doesn't have its own export).

Example: router/api/posts/route.ts → serves /api/posts for any method you export.

2. One file per method: get.ts, post.route.ts, etc.

  • Names: get.ts, post.ts, get.route.ts, post.route.ts, etc. (same for put, patch, delete, head, options).
  • Extensions: .ts, .js, .mts, .mjs
  • Meaning: That path handles only that HTTP method. You can mix: e.g. route.ts for GET and post.route.ts for POST on the same path.
  • Exports: Export the handler as the method name (e.g. export function GET) or as default.

You cannot have two files that both handle the same method for the same path (e.g. two files for GET on /api/users); the loader will throw.


How URL paths are built

  • The pathname of a route is the directory path to the folder that contains the route file. The filename does not appear in the URL.
  • Route groups — A folder whose name is in parentheses is omitted from the URL:
    • router/api/(user)/users/route.ts → pathname is /api/users (not /api/(user)/users).
  • Dynamic segments — A folder whose name is in square brackets becomes a path parameter:
    • router/api/posts/[postId]/route.ts → pathname /api/posts/:postId; the matched value is in ctx.params.postId.
  • Optional catch-all[[...param]] matches the rest of the path (including zero segments). The param name (e.g. all) receives the remainder as a single string.

Examples:

File pathURL path
router/route.ts/
router/api/hello/route.ts/api/hello
router/api/(user)/users/get.route.tsGET /api/users
router/api/(user)/posts/[postId]/route.ts/api/posts/:postId
router/api/(user)/posts/[postId]/author/route.ts/api/posts/:postId/author

Middleware file naming

  • Names: middleware.ts, middleware.js, middleware.mts, middleware.mjs
  • Placement: Only under router/. The directory path under router/ is the path prefix for which the middleware runs. Middleware in a folder runs for that path and all subpaths.
  • Order: Middleware is resolved from the root toward the request path (e.g. router/middleware.ts then router/api/middleware.ts for a request to /api/hello).

Event handlers: appDir/events/

  • Directory: Only appDir/events/ is scanned for event handlers.
  • File names: *.event.ts, *.event.js, *.event.mts, *.event.mjs
  • Event name: Derived from the filename (before .event.*) by converting camelCase to dot.case:
    • userCreatedEmail.event.ts → event name user.created.email
  • Export: The file must export a default function; that function is registered as the handler for that event name. You emit events via ctx.events.emit('user.created.email', payload) (or the event bus API).

Scheduled tasks: appDir/cron/

  • Directory: Only appDir/cron/ is scanned for scheduled tasks.
  • File names: *.cron.ts, *.cron.js, *.cron.mts, *.cron.mjs
  • Task name: Derived from the filename (before .cron.*) by converting camelCase to dot.case:
    • intervalExample.cron.ts → schedule name interval.example
  • Exports: The file must export schedule (cron expression or schedule definition) and run (function). You can invoke the same run logic by name via ctx.schedule('interval.example', payload) (or the schedule runner API).

Config and extensions summary

KindLocationFile patternNotes
RoutesAny folder under router/route.ts, get.ts, post.route.ts, etc.Folder path under router/ = URL path; (group) omitted, [id] = param
MiddlewareAny folder under router/middleware.tsPath prefix = directory path under router/
EventsappDir/events/ only*.event.tsEvent name = camelCase → dot.case
SchedulesappDir/cron/ only*.cron.tsTask name = camelCase → dot.case

Supported extensions for routes and middleware: .ts, .js, .mts, .mjs. Use the same extensions for events and cron files.


Next steps

  • Get Started — Set up a project and run your first route.
  • Routing — Dynamic segments, route groups, and method handlers in more detail.
  • Middleware — Order, next(), and error handling.