LogoFletch
Discord
fletchkartikey321/fletch 999999

Routing#

Fletch uses a fast radix-tree router to match incoming requests to handlers.

Basic Routes#

Define routes for different HTTP methods:

final app = Fletch();

app.get('/users', (req, res) {
  res.json({'users': []});
});

app.post('/users', (req, res) async {
  final body = await req.body;
  res.status(201).json(body);
});

app.put('/users/:id', (req, res) {
  res.json({'updated': req.params['id']});
});

app.delete('/users/:id', (req, res) {
  res.status(204).send();
});

Note: Route methods (get, post, etc.) return void, so they cannot be chained.

Path Parameters#

Capture dynamic segments from the URL:

app.get('/users/:userId/posts/:postId', (req, res) {
  final userId = req.params['userId'];
  final postId = req.params['postId'];
  
  res.json({
    'userId': userId,
    'postId': postId,
  });
});

Access via: /users/123/posts/456

Query Parameters#

Access query string parameters using req.query:

app.get('/search', (req, res) {
  // /search?q=dart&page=2
  final query = req.query['q'];
  final page = int.tryParse(req.query['page'] ?? '1') ?? 1;
  
  res.json({
    'query': query,
    'page': page,
  });
});

Wildcard Routes#

Match multiple path segments using *:

app.get('/files/*', (req, res) {
  // Access the matched wildcard value via regular params or specialized logic
  // Typically, wildcards match everything remaining in the path
  res.text('Matched file request');
});

Access /files/documents/report.pdf triggers this handler.

Controllers#

For larger applications, organize routes using the Controller class.

  1. Create a controller by extending Controller.
  2. Override registerRoutes to define endpoints.
  3. Mount it using useController.
import 'package:fletch/fletch.dart';

class UserController extends Controller {
  @override
  void registerRoutes(ControllerOptions options) {
    // GET /users/
    options.get('/', getAll);
    
    // GET /users/:id
    options.get('/:id', getById);
    
    // POST /users/
    options.post('/', create);
  }

  void getAll(Request req, Response res) {
    res.json(['user1', 'user2']);
  }

  void getById(Request req, Response res) {
    res.json({'id': req.params['id']});
  }

  Future<void> create(Request req, Response res) async {
    final body = await req.body;
    res.status(201).json(body);
  }
}

void main() {
  final app = Fletch();
  
  // Mounts all controller routes under /users
  app.useController('/users', UserController());
  
  app.listen(3000);
}

This pattern keeps your main() function clean and your route logic modular.

Route Groups#

You can also group routes using base_container organization or by simply mounting IsolatedContainers for larger features.

See Isolated Containers for advanced grouping.

Route Order#

Routes are matched based on the radix tree structure. Specific static paths generally take precedence, but parameterized routes are matched when no static path exists.

// Static path
app.get('/users/me', (req, res) { ... });

// Parameterized path
app.get('/users/:id', (req, res) { ... });

Requesting /users/me will match the first handler. Requesting /users/123 will match the second.

All Methods#

Handle any HTTP method for a path:

app.all('/api/*', (req, res, next) async {
  print('API called: ${req.method} ${req.uri.path}');
  await next();
});