Setup & Scaffolding
In this section, we'll initialize our project and set up the core dependencies. We're building a "server-side" application, so we'll start with the standard Dart server template.
Create the project
First, create a new Dart project using the server-shelf template. This gives us a basic Shelf server structure to build upon.
dart create -t server-shelf packages/ormed/example/fullstack
Add dependencies
We use a carefully selected set of packages to handle different aspects of our full-stack application. Here's a breakdown of what each package does:
Core Framework
- shelf: The standard web server middleware for Dart. It's lightweight and highly extensible.
- shelf_router: A powerful router for Shelf that makes defining web and API endpoints easy.
- shelf_multipart: Adds multipart/form-data support to Shelf for handling file uploads.
- shelf_static: Serves static files (like uploaded images) from the file system.
Database (Ormed)
- ormed: Our main ORM. It provides a type-safe way to interact with the database using Dart models.
- ormed_sqlite: The SQLite driver for Ormed. We'll use SQLite for its simplicity and zero-config setup.
UI & Templates
- liquify: A flexible template engine (Liquid-compatible) for rendering HTML on the server.
Storage & Files
- file_cloud: A cloud-agnostic storage abstraction.
- storage_fs: A local file system implementation for
file_cloud, perfect for development.
Observability
- contextual: A structured logging library that allows you to attach context to your logs.
- contextual_shelf: Middleware to automatically inject request context (like Request IDs) into your logs.
Testing
- server_testing: A library for testing HTTP servers, supporting both in-memory and live server testing.
- server_testing_shelf: Shelf-specific bindings for
server_testing. - property_testing: A property-based testing framework for Dart, used for stress testing and finding edge cases.
- assertable_json: A fluent API for asserting on JSON structures in tests.
CLI & Utilities
- artisanal: A toolkit for building Laravel-style CLI commands (Artisan).
- pubspec.yaml
name: ormed_fullstack_example
description: Full-stack movie catalog example using Ormed, Shelf, Liquify, and structured logging.
version: 1.0.1
# repository: https://github.com/my_org/my_repo
environment:
sdk: ^3.10.4
dependencies:
artisanal: ^0.1.2
contextual: ^2.1.0
contextual_shelf: ^0.3.0
file_cloud: ^0.1.0
liquify: ^1.4.1
ormed: ^0.1.0
ormed_cli: ^0.1.0
ormed_sqlite: ^0.1.0
path: ^1.9.1
shelf: ^1.4.2
shelf_multipart: ^2.0.1
shelf_router: ^1.1.4
shelf_static: ^1.1.3
storage_fs: ^0.1.0
dev_dependencies:
assertable_json: ^0.2.1
build_runner: ^2.10.4
http: ^1.6.0
lints: ^6.0.0
property_testing: ^0.2.1+1
server_testing: ^0.2.0
server_testing_shelf: ^0.2.1+1
test: ^1.28.0
dependency_overrides:
ormed: { path: ../.. }
ormed_cli: { path: ../../../ormed_cli }
ormed_mysql: { path: ../../../ormed_mysql }
ormed_postgres: { path: ../../../ormed_postgres }
ormed_sqlite: { path: ../../../ormed_sqlite }
Initialize Ormed
Once the dependencies are added, we need to initialize Ormed. This creates the ormed.yaml configuration file and sets up the project for code generation.
dart run ormed_cli:ormed init --no-interaction
The ormed.yaml file tells the ORM where to find your models and where to generate the registry.
- ormed.yaml
driver:
type: sqlite
options:
database: database/ormed_fullstack_example.sqlite
migrations:
directory: lib/src/database/migrations
registry: lib/src/database/migrations.dart
ledger_table: orm_migrations
schema_dump: database/schema.sql
seeds:
directory: lib/src/database/seeders
registry: lib/src/database/seeders.dart
DataSource helper
To make database access consistent across our app and tests, we use a createDataSource helper. This helper is automatically generated when you run ormed init, and it handles loading the configuration and bootstrapping the ORM.
/// Creates a new DataSource instance using the project configuration.
DataSource createDataSource() {
ensureSqliteDriverRegistration();
final config = loadOrmConfig();
return DataSource.fromConfig(config, registry: bootstrapOrm());
}
We then wrap this in an AppDatabase class to manage the connection lifecycle within our application.
class AppDatabase {
AppDatabase() : _ownsDataSource = true;
AppDatabase.fromDataSource(this.dataSource) : _ownsDataSource = false;
late final DataSource dataSource;
final bool _ownsDataSource;
Future<void> init() async {
if (_ownsDataSource) {
dataSource = createDataSource();
}
await dataSource.init();
}
OrmConnection get connection => dataSource.connection;
Future<void> dispose() {
if (_ownsDataSource) {
return dataSource.dispose();
}
return Future.value();
}
}