Skip to content

File Routing

Spry v7 does not want you registering routes by hand. The filesystem is the source of truth.

Start from routes/

The scanner walks every Dart file under routes/ and turns it into a route, scoped middleware binding, or scoped error boundary.

text
routes/
  index.dart
  about.get.dart
  users/[id].dart
  [...slug].dart
  _middleware.dart
  _error.dart

Route files

index.dart

routes/index.dart maps to /.

dart
import 'package:spry/spry.dart';

Response handler(Event event) {
  return Response.json({
    'message': 'hello from spry',
    'runtime': event.context.runtime.name,
    'path': event.request.url.path,
  });
}

Method-specific files

Append an HTTP method to restrict a file to that method:

dart
import 'package:spry/spry.dart';

Response handler(Event event) {
  return Response.json({
    'page': 'about',
    'method': event.method,
  });
}

That file maps to GET /about.

Supported suffixes are:

  • .get
  • .post
  • .put
  • .patch
  • .delete
  • .head
  • .options

Dynamic params

Square brackets create named params:

dart
// ignore_for_file: file_names

import 'package:spry/spry.dart';

Response handler(Event event) {
  final id = event.params.required('id');
  return Response.json({
    'id': id,
    'upper': id.toUpperCase(),
  });
}

routes/users/[id].dart maps to /users/:id.

Catch-all files

Use [...name].dart for remainder matches:

dart
import 'package:spry/spry.dart';

Response handler(Event event) {
  return Response.json({
    'fallback': true,
    'slug': event.params.wildcard,
  }, status: 404);
}

The wildcard value is available through event.params.wildcard.

routes/[...slug].dart maps to /**:slug.

Expressive segment syntax

Spry now forwards richer pathname syntax into roux through filesystem-safe file names:

File or folder nameRoute segment
[id]:id
[id([0-9]+)]:id([0-9]+)
[[id]]:id?
[...slug]**:slug
[...]**
[...path+]:path+
[[...path]]:path*
[_]*
[name].[ext]:name.:ext
post-[id].jsonpost-:id.json

Notes:

  • [...name] is a terminal remainder matcher. It can only appear at the end of a route.
  • [_] is a single-segment wildcard. It matches exactly one path segment.
  • [[name]], [...path+], and [[...path]] let one file cover optional or repeated suffix segments.
  • Embedded params work anywhere inside a segment, so dots and literal prefixes stay intact.

Scoped files

_middleware.dart

This file wraps all matching routes in the current directory scope:

dart
import 'package:spry/spry.dart';

Future<Response> middleware(Event event, Next next) async {
  event.locals.set(#requestId, DateTime.now().microsecondsSinceEpoch.toString());
  return next();
}

_error.dart

This file catches errors thrown by matching routes in the current scope:

dart
import 'package:spry/spry.dart';

Response onError(Object error, StackTrace stackTrace, Event event) {
  if (error case NotFoundError()) {
    return Response.json({
      'error': 'not_found',
      'path': event.request.url.path,
    }, status: 404);
  }
  if (error case HTTPError()) {
    return error.toResponse();
  }

  return Response.json({
    'error': 'internal_server_error',
    'path': event.request.url.path,
  }, status: 500);
}

Global middleware

Files in top-level middleware/ are collected separately and executed in filename order:

dart
// ignore_for_file: file_names

import 'package:spry/spry.dart';

Future<Response> middleware(Event event, Next next) async {
  final startedAt = DateTime.now();
  final response = await next();
  final duration = DateTime.now().difference(startedAt).inMilliseconds;
  print('${event.request.method} ${event.request.url.path} -> ${response.status} (${duration}ms)');
  return response;
}

Scope rules that matter

  • Files or folders that start with _ are reserved for framework behavior.
  • Catch-all directories cannot also define scoped middleware or scoped error files under the same wildcard shape.
  • Spry rejects duplicate routes and param-name drift for the same route shape during scanning.
  • The root fallback is the root-level catch-all route when present.

Released under the MIT License.