Skip to content

Project Structure

Spry is easiest to understand when you start from the project tree, not from framework internals.

Default layout

text
.
├─ routes/
│  ├─ index.dart
│  ├─ users/[id].dart
│  ├─ _middleware.dart
│  └─ _error.dart
├─ middleware/
│  └─ 01_logger.dart
├─ public/
│  └─ hello.txt
├─ hooks.dart
└─ spry.config.dart

Each part has a narrow job:

  • routes/ defines route handlers
  • middleware/ defines global middleware
  • public/ holds static assets served directly
  • hooks.dart defines lifecycle hooks
  • spry.config.dart configures target, output, and local runtime behavior

What belongs where

routes/

Put request handlers here. This is the center of the app.

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,
  });
}

middleware/

Put global request behavior here when it should apply broadly across the app.

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;
}

public/

Put static files here when they should be served without going through a route handler.

text
public/
  robots.txt
  logo.svg
  hello.txt

hooks.dart

Put startup and shutdown behavior here:

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

Future<void> onStart(ServerLifecycleContext context) async {
  print('Spry example started: ${context.runtime.name}');
}

spry.config.dart

Put runtime choice and build behavior here:

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

void main() {
  defineSpryConfig(
    host: '127.0.0.1',
    port: 4000,
    target: BuildTarget.dart,
  );
}

Why this structure works

Spry is opinionated about where files live so that the happy path stays obvious:

  • route files are easy to scan
  • cross-cutting behavior has a small number of entrypoints
  • runtime choice is separated from route logic

That is the real authoring model. You should not need to think about generated internals during normal development.

Released under the MIT License.