Skip to content

Step 3: Routes and controller

This step adds a resource controller and the routes to list, create and show posts.

1. Make the controller

dox create:controller Post -r

-r makes a resource controller with the seven CRUD methods. Replace the file with this. It keeps only index, store and show:

lib/app/http/controllers/post.controller.dart
import 'package:blog/app/models/post/post.model.dart';
import 'package:dox_core/dox_core.dart';

class PostController {
  /// GET /api/posts
  Future<List<Post>> index(DoxRequest req) async {
    return Post().all();
  }

  /// POST /api/posts
  Future<DoxResponse> store(DoxRequest req) async {
    Post post = Post()
      ..title = req.input('title')
      ..body = req.input('body');
    await post.save();
    return response(post).statusCode(201);
  }

  /// GET /api/posts/{id}
  Future<Post> show(int id) async {
    Post? post = await Post().find(id);
    if (post == null) {
      throw NotFoundHttpException();
    }
    return post;
  }
}

PostController postController = PostController();

How it works:

  • A controller returns a model or a list of models. Dox sends it as JSON.
  • response(post).statusCode(201) sets the status code.
  • show(int id) gets the {id} from the URL. /api/posts/abc answers 404, because abc is not an int.
  • req.input('title') reads the body as it is. It is not checked yet. Step 4 fixes that.

2. Add the routes

lib/routes/api.dart
import 'package:blog/app/http/controllers/api.controller.dart';
import 'package:blog/app/http/controllers/post.controller.dart';
import 'package:dox_core/dox_core.dart';

class ApiRouter extends Router {
  @override
  String get prefix => 'api';

  @override
  List<dynamic> get middleware => <dynamic>[];

  @override
  void register() {
    Route.get('/ping', apiController.pong);

    Route.resource('posts', postController);
  }
}

Route.resource makes one route for each method the controller has:

Method Path Controller method
GET /api/posts index
POST /api/posts store
GET /api/posts/{id} show

The controller has no update or destroy, so those routes are not made.

3. Start the server

dox s

If it is still running from step 1, it restarts by itself.

Check it

Make a post:

curl -i -X POST http://localhost:3000/api/posts \
  -H 'Content-Type: application/json' \
  -d '{"title":"Hello Dox","body":"My first post."}'
HTTP/1.1 201 Created
content-type: application/json; charset=utf-8
...

{"id":1,"title":"Hello Dox","body":"My first post.","created_at":"2026-10-22T13:58:14.062215Z","updated_at":"2026-10-22T13:58:14.062215Z"}

List the posts:

curl http://localhost:3000/api/posts
[{"id":1,"title":"Hello Dox","body":"My first post.","created_at":"2026-10-22T13:58:14.062215Z","updated_at":"2026-10-22T13:58:14.062215Z"}]

Show one post:

curl http://localhost:3000/api/posts/1
{"id":1,"title":"Hello Dox","body":"My first post.","created_at":"2026-10-22T13:58:14.062215Z","updated_at":"2026-10-22T13:58:14.062215Z"}

A post that does not exist:

curl -i http://localhost:3000/api/posts/99
HTTP/1.1 404 Not Found
...

{"code":"not_found","message":"Not Found"}

Next

Step 4: Validation