Use with the gisila framework
You’ll connect a gisila web app to PostgreSQL through gisila_orm, query from a service, turn constraint violations into proper HTTP errors, and expose a health endpoint.
Prerequisites
Section titled “Prerequisites”- A gisila app (see the framework docs).
- gisila_orm set up with a generated schema, as in Set up a project.
1. Wire the database into the app
Section titled “1. Wire the database into the app”bin/server.dart:
import 'package:gisila/gisila.dart';import 'package:gisila_orm/gisila.dart';import 'package:gisila_orm/web.dart';
Future<void> main() async { final db = await Database.connect( await DatabaseConfig.fromEnvironment(), onQuery: slowQueryLogger(const Duration(milliseconds: 200), print), ); await db.ping(); // fail at startup, not on the first request
final orm = GisilaOrmDatabaseProvider(db); final app = GisilaApp( config: AppConfig( database: orm, dbErrorMapper: const PostgresDbErrorMapper(), healthCheckPath: '/healthz', healthCheck: orm.isHealthy, ), );
app.registerService<AuthorService>(AuthorService.new); app.registerController(attacher: AuthorsController().attachToApp);
await app.start(host: '0.0.0.0', port: 8080);}| Setting | Effect |
|---|---|
database: orm |
Each request can read the shared Database with ctx.db<Database>(). The pool closes when the app stops. |
dbErrorMapper |
Uncaught constraint violations become 409 / 400 responses instead of 500s. |
healthCheck: orm.isHealthy |
/healthz answers 503 while SELECT 1 fails. |
2. Query from a service
Section titled “2. Query from a service”import 'package:blog/models/blog.gisila.g.dart';import 'package:gisila/gisila.dart';import 'package:gisila_orm/gisila.dart';
class AuthorService extends Service { DbContext get _db => db<Database>().context();
Future<List<Author>> list({int limit = 50, int? after}) => authors() .after(AuthorTable.id, after) .limit(limit.clamp(1, 100)) .all(_db);
Future<Author?> byId(int id) => authors().where(AuthorTable.id.eq(id)).first(_db);
Future<Author> create(String name, String email) => authors().insert({'name': name, 'email': email}).one(_db);
Future<Post> createWithFirstPost(String name, String email, String title) => db<Database>().transaction((tx) async { final author = await authors().insert({'name': name, 'email': email}).one(tx); return posts().insert({'title': title, 'author_id': author.id}).one(tx); });}The provider doesn’t open a transaction per request. Open one in the service
method that needs it, as createWithFirstPost does.
3. Expose it from a controller
Section titled “3. Expose it from a controller”import 'package:gisila/gisila.dart';
part 'authors_controller.g.dart';
@Controller('/authors')class AuthorsController { @Get('/') Future<List<Map<String, Object?>>> list(AuthorService authors, {int? after}) async => [for (final a in await authors.list(after: after)) a.toJson(exclude: ['email'])];
@Post('/') Future<Map<String, Object?>> create(CreateAuthorForm form, AuthorService authors) async => (await authors.create(form.name.value!, form.email.value!)).toJson();}CreateAuthorForm stands for a gisila form class with name and email
fields. Return toJson() maps (with exclude / only to control fields)
rather than models, so the response shape is explicit. See the framework docs
for forms, validation and code generation.
4. Rely on the error mapper
Section titled “4. Rely on the error mapper”Creating an author with an email that exists raises
PostgresUniqueViolationException. With PostgresDbErrorMapper the client gets:
{ "error": { "status": 409, "message": "\"email\" is already taken.", "code": "unique_violation", "details": { "fields": ["email"] } }}with HTTP status 409. The envelope is gisila’s standard error format; the
mapper supplies the status, message, code and details. The submitted value
is never echoed back.
Catch the exception in the service when you want a different message.
Verify it works
Section titled “Verify it works”dart run bin/server.dart &curl -s localhost:8080/healthzcurl -s -X POST localhost:8080/authors -H 'content-type: application/json' \ -d '{"name":"Ada","email":"ada@example.com"}'curl -s -o /dev/null -w '%{http_code}\n' -X POST localhost:8080/authors \ -H 'content-type: application/json' -d '{"name":"Ada","email":"ada@example.com"}'# 409