In this tutorial you connect endpoints to a database with sqlite3. You register the connection and a repository through
dependency injection. The pattern works the same with any driver (Postgres, MySQL and others): only the code inside the repository changes. The examples assume your package is named
my_app.
Add the driver#
dependencies:
sqlite3: ^2.9.0
Write a repository#
The repository holds all of the database-specific code:
import 'package:sqlite3/sqlite3.dart';
class TodoRepository {
TodoRepository(this._db) {
_db.execute('''
CREATE TABLE IF NOT EXISTS todos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL
);
''');
}
final Database _db;
List<Map<String, Object?>> findAll() {
final result = _db.select('SELECT id, title FROM todos ORDER BY id');
return [
for (final row in result) {'id': row['id'], 'title': row['title']},
];
}
int insert(String title) {
_db.execute('INSERT INTO todos (title) VALUES (?)', [title]);
return _db.lastInsertRowId;
}
}
Register the connection and repository#
Register the database and the repository as lazy singletons, so each is created once, the first time it's used.
TodoRepository's constructor takes an argument, so register it with a closure that calls
di.get<Database>():
import 'package:my_app/repos/todo_repository.dart';
import 'package:revali_router/revali_router.dart';
import 'package:sqlite3/sqlite3.dart';
@App()
final class MainApp extends AppConfig {
const MainApp() : super(host: 'localhost', port: 8080);
@override
Future<void> configureDependencies(DI di) async {
di
..registerLazySingleton<Database>(sqlite3.openInMemory)
..registerLazySingleton<TodoRepository>(
() => TodoRepository(di.get<Database>()),
);
}
}
sqlite3.openInMemory() needs no setup, and the data lasts only as long as the server process. To keep the data on disk, use
sqlite3.open('todos.db') instead.
Inject the repository into a controller#
Revali resolves controller constructor parameters from DI automatically. @Dep() is optional here:
import 'package:my_app/repos/todo_repository.dart';
import 'package:revali_router/revali_router.dart';
@Controller('todos')
class TodoController {
const TodoController({@Dep() required this.repo});
final TodoRepository repo;
@Get()
List<Map<String, Object?>> list() => repo.findAll();
@Post()
Map<String, Object?> create(@Body(['title']) String title) {
final id = repo.insert(title);
return {'id': id, 'title': title};
}
}
curl -X POST http://localhost:8080/api/todos -H 'Content-Type: application/json' -d '{"title": "Buy milk"}'
# {"data":{"id":1,"title":"Buy milk"}}
curl http://localhost:8080/api/todos
# {"data":[{"id":1,"title":"Buy milk"}]}
@Body(['title']) reads the title field from the JSON body. If it's missing, the request gets a 400. Every request uses the same
Database, because it's a singleton.
Next: Configure Dependencies (including request-scoped transactions) · Body binding · Testing