Hey guys! Welcome to Part 2 of our Dart Frog series. If you missed Part 1, we set up Dart Frog and built a basic API with hot reload. Watch it now if you’re new!
Today, we’re leveling up: Building your first real REST API, a full CRUD Todo endpoint, clean, and production-ready backends in pure Dart.
We’ll use dynamic routes, UUIDs, validation, and proper errors. By the end, you’ll have a testable API ready for your Flutter app next video. Let’s jump in!
Planning & Best Practices
Quick plan: We’ll create a Todo model with id, title, and completed status. Store them in-memory (Map for fast lookup), perfect for learning, and easy to upgrade to Postgres or Drift later.
Best practices:
- Dynamic routes with [id].dart
- UUID package for unique IDs
- Validate JSON bodies
- Return correct status: 200, 201, 404, 400.
Open your project from Part 1 — or create a new one with dart_frog create todo_api. Run dart_frog dev.
First, add UUID: pubspec.yaml
dependencies:
uuid: ^4.5.0
flutter pub get (or dart pub get).
Create model: lib/src/todo.dart
///
class Todo {
///
Todo({required this.id, required this.title, this.isCompleted = false});
/// fromJson
factory Todo.fromJson(Map<String, dynamic> json) {
return Todo(
id: json['id'] as String,
title: json['title'] as String,
isCompleted: json['isCompleted'] as bool? ?? false,
);
}
/// id
final String id;
/// title
final String title;
/// isCompleted
bool isCompleted;
/// toJson
Map<String, dynamic> toJson() {
return {
'id': id,
'title': title,
'isCompleted': isCompleted,
};
}
}
In-memory store: lib/src/todo_repository.dart
import 'package:my_project/src/todo_model.dart';
import 'package:uuid/uuid.dart';
const _uuid = Uuid();
final _todos = <String, Todo>{};
/// get all todos
List<Todo> getAllTodos() => _todos.values.toList();
/// get a tod
Todo? getTodoById(String id) => _todos[id];
/// create
void createTodo(String title) {
final id = _uuid.v4();
_todos[id] = Todo(id: id, title: title);
}
/// update
void updateTodo(String id, {String? title, bool? isCompleted}) {
final todo = _todos[id];
if (todo == null) return;
_todos[id] = Todo(
id: id,
title: title ?? todo.title,
isCompleted: isCompleted ?? todo.isCompleted,
);
}
/// delete
void deleteTodo(String id) => _todos.remove(id);
Now routes!
Collection: routes/todos/index.dart
import 'package:dart_frog/dart_frog.dart';
import 'package:my_project/src/todo_repository.dart';
Future<Response> onRequest(RequestContext context) async {
switch (context.request.method) {
case HttpMethod.get:
final todos = getAllTodos();
return Response.json(body: todos.map((e) => e.toJson()).toList());
case HttpMethod.post:
final body = await context.request.json() as Map<String, dynamic>;
final title = body['title'] as String?;
if (title == null || title.isEmpty) {
return Response(statusCode: 400, body: 'Title is required');
}
createTodo(title);
return Response(statusCode: 201, body: 'Todo created');
case HttpMethod.delete:
case HttpMethod.put:
case HttpMethod.patch:
case HttpMethod.head:
case HttpMethod.options:
return Response(statusCode: 405);
}
}
Dynamic item: routes/todos/[id].dart
import 'package:dart_frog/dart_frog.dart';
import 'package:my_project/src/todo_repository.dart';
Future<Response> onRequest(RequestContext context, String id) async {
final todo = getTodoById(id);
if (todo == null) return Response(statusCode: 404);
switch (context.request.method) {
case HttpMethod.get:
return Response.json(body: todo.toJson());
case HttpMethod.put:
final body = await context.request.json() as Map<String, dynamic>;
final title = body['title'] as String?;
final isCompleted = body['isCompleted'] as bool?;
updateTodo(id, title: title, isCompleted: isCompleted);
return Response.json(body: getTodoById(id)!.toJson());
case HttpMethod.delete:
deleteTodo(id);
return Response(statusCode: 204);
case HttpMethod.post:
case HttpMethod.patch:
case HttpMethod.head:
case HttpMethod.options:
return Response(statusCode: 405);
}
}
Testing + Wrap (Show curl/Postman)
Quick tests:
curl http://localhost:8080/todos
curl -X POST http://localhost:8080/todos -H "Content-Type: application/json" -d '{"title": "Learn Dart Frog"}'
curl http://localhost:8080/todos/<generated-id>
Handles errors gracefully. Production-ready foundation!
Source Code 👇 — Show some ❤️ by starring ⭐ the repo and follow me 😄! https://github.com/techwithsam/dart_frog_full_course_tutorial
This is your first real Dart Frog REST API — congrats! Next: Connect a Flutter app to it.
Samuel Adekunle, Tech With Sam YouTube
Top comments (0)