Overview
This example demonstrates a complete backend API built with Dart Frog and Nest Dart, featuring:
Modular architecture with dependency injection
SQLite database integration
RESTful API endpoints
Repository pattern
Service layer abstraction
Middleware integration
Configuration management
Project Structure
frog_backend/
├── lib/
│ ├── app_module.dart # Root application module
│ ├── core/
│ │ └── core.dart
│ ├── database/
│ │ └── database_module.dart # Database configuration
│ └── modules/
│ ├── core_module.dart # Config & logging services
│ └── user_module.dart # User domain module
├── routes/
│ ├── _middleware.dart # Global middleware
│ ├── index.dart # Root endpoint
│ └── users/
│ ├── index.dart # User list/create
│ └── [id].dart # User detail
├── test/
└── pubspec.yaml
Getting Started
Install Dependencies
Navigate to the example directory and install dependencies: cd examples/frog_backend
dart pub get
Start the Development Server
Run the Dart Frog development server: The server will start at http://localhost:8080
Test the API
Try the API endpoints: Get Welcome Message
List Users
Create User
Get User by ID
curl http://localhost:8080
Module Setup
App Module
The root module orchestrates all feature modules:
import 'package:nest_core/nest_core.dart' ;
import 'database/database_module.dart' ;
import 'modules/core_module.dart' ;
import 'modules/user_module.dart' ;
// Main application module
class AppModule extends Module {
@override
List < Module > get imports => [ DatabaseModule (), CoreModule (), UserModule ()];
@override
Future < void > providers ( Locator locator) async {}
}
Database Module
Sets up SQLite database with schema initialization:
lib/database/database_module.dart
import 'package:nest_core/nest_core.dart' ;
import 'package:sqlite3/sqlite3.dart' ;
class DatabaseService {
final Database _database;
DatabaseService ( this ._database);
/// Execute a query and return results
ResultSet query ( String sql, [ List < Object ?> parameters = const []]) {
return _database. select (sql, parameters);
}
/// Execute a statement (INSERT, UPDATE, DELETE)
void execute ( String sql, [ List < Object ?> parameters = const []]) {
_database. execute (sql, parameters);
}
/// Execute a statement and return the number of affected rows
int executeAndReturnChanges (
String sql,
[ List < Object ?> parameters = const []],
) {
_database. execute (sql, parameters);
return _database.updatedRows;
}
/// Get the last inserted row ID
int get lastInsertRowId => _database.lastInsertRowId;
/// Execute code within a transaction
T transaction < T >( T Function () action) {
beginTransaction ();
try {
final result = action ();
commitTransaction ();
return result;
} catch (e) {
rollbackTransaction ();
rethrow ;
}
}
void beginTransaction () => _database. execute ( 'BEGIN TRANSACTION' );
void commitTransaction () => _database. execute ( 'COMMIT' );
void rollbackTransaction () => _database. execute ( 'ROLLBACK' );
void close () => _database. dispose ();
}
class DatabaseModule extends Module {
@override
List < Module > get imports => [];
@override
List < Type > get exports => [ DatabaseService ];
@override
Future < void > providers ( Locator locator) async {
final database = sqlite3. open ( 'app.db' );
_initializeSchema (database);
// Register SQLite database instance
locator. registerSingleton < Database >(database);
// Register DatabaseService
locator. registerSingleton < DatabaseService >( DatabaseService (database));
}
void _initializeSchema ( Database db) {
// Create users table
db. execute ( '''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL
)
''' );
}
}
Core Module
Provides shared services like configuration and logging:
lib/modules/core_module.dart
import 'package:nest_core/nest_core.dart' ;
class ConfigService {
final Map < String , dynamic > _config = {
'database_type' : 'in-memory' ,
'port' : 8080 ,
'environment' : 'development' ,
'jwt_secret' : 'your-secret-key' ,
'app_name' : 'Dart Frog Demo API' ,
'version' : '1.0.0' ,
};
T get < T >( String key) => _config[key] as T ;
String get databaseType => get < String >( 'database_type' );
int get port => get < int >( 'port' );
String get environment => get < String >( 'environment' );
String get jwtSecret => get < String >( 'jwt_secret' );
String get appName => get < String >( 'app_name' );
String get version => get < String >( 'version' );
}
class LoggerService {
void log ( String message) {
print ( '[LOG] ${ DateTime . now ()} : $ message ' );
}
void error ( String message) {
print ( '[ERROR] ${ DateTime . now ()} : $ message ' );
}
}
class CoreModule extends Module {
@override
Future < void > providers ( Locator locator) async {
locator. registerSingleton < ConfigService >( ConfigService ());
locator. registerSingleton < LoggerService >( LoggerService ());
}
@override
List < Type > get exports => [ ConfigService , LoggerService ];
}
User Module
Implements the complete user domain with repository and service layers:
lib/modules/user_module.dart
import 'package:nest_core/nest_core.dart' ;
import '../database/database_module.dart' ;
import 'core_module.dart' ;
class User {
final int id;
final String name;
final String email;
const User ({ required this .id, required this .name, required this .email});
factory User . fromMap ( Map < String , dynamic > map) {
return User (
id : map[ 'id' ] as int ,
name : map[ 'name' ] as String ,
email : map[ 'email' ] as String ,
);
}
Map < String , dynamic > toJson () => { 'id' : id, 'name' : name, 'email' : email};
}
class UserRepository {
final DatabaseService _database;
final LoggerService _logger;
UserRepository ( this ._database, this ._logger);
List < User > findAll () {
_logger. log ( 'Finding all users' );
final results = _database. query ( 'SELECT * FROM users ORDER BY id' );
return results. map ((row) => User . fromMap (row)). toList ();
}
User ? findById ( int id) {
_logger. log ( 'Finding user by id: $ id ' );
final results = _database. query ( 'SELECT * FROM users WHERE id = ?' , [id]);
if (results.isEmpty) return null ;
return User . fromMap (results.first);
}
User ? create ( String name, String email) {
_logger. log ( 'Creating user: $ name ' );
_database. execute ( 'INSERT INTO users (name, email) VALUES (?, ?)' , [
name,
email,
]);
final id = _database.lastInsertRowId;
return User (id : id, name : name, email : email);
}
bool update ( int id, { String ? name, String ? email}) {
_logger. log ( 'Updating user: $ id ' );
final updates = < String > [];
final params = < Object ?> [];
if (name != null ) {
updates. add ( 'name = ?' );
params. add (name);
}
if (email != null ) {
updates. add ( 'email = ?' );
params. add (email);
}
if (updates.isEmpty) return false ;
params. add (id);
final sql = 'UPDATE users SET ${ updates . join ( ', ' )} WHERE id = ?' ;
final changes = _database. executeAndReturnChanges (sql, params);
return changes > 0 ;
}
bool delete ( int id) {
_logger. log ( 'Deleting user: $ id ' );
final changes = _database. executeAndReturnChanges (
'DELETE FROM users WHERE id = ?' ,
[id],
);
return changes > 0 ;
}
}
class UserService {
final UserRepository _repository;
final LoggerService _logger;
UserService ( this ._repository, this ._logger);
List < User > getAllUsers () {
_logger. log ( 'UserService: Getting all users' );
return _repository. findAll ();
}
User ? getUserById ( int id) {
_logger. log ( 'UserService: Getting user by id: $ id ' );
return _repository. findById (id);
}
User ? createUser ( String name, String email) {
_logger. log ( 'UserService: Creating user: $ name ' );
return _repository. create (name, email);
}
bool updateUser ( int id, { String ? name, String ? email}) {
_logger. log ( 'UserService: Updating user: $ id ' );
return _repository. update (id, name : name, email : email);
}
bool deleteUser ( int id) {
_logger. log ( 'UserService: Deleting user: $ id ' );
return _repository. delete (id);
}
}
class UserModule extends Module {
@override
List < Module > get imports => [ DatabaseModule (), CoreModule ()];
@override
Future < void > providers ( Locator locator) async {
locator. registerSingleton < UserRepository >(
UserRepository (
locator. get < DatabaseService >(),
locator. get < LoggerService >(),
),
);
locator. registerSingleton < UserService >(
UserService (locator. get < UserRepository >(), locator. get < LoggerService >()),
);
}
}
Notice the layered architecture: Repository handles data access, Service handles business logic, and Controllers (route handlers) handle HTTP.
Middleware Integration
The middleware initializes Nest modules and makes services available to routes:
import 'package:dart_frog/dart_frog.dart' ;
import 'package:nest_frog_backend/app_module.dart' ;
import 'package:nest_frog/nest_frog.dart' ;
final message = 'Welcome to Dart Frog with NestJS-like Modules!' ;
Handler middleware ( Handler handler) {
if ( ! Modular .isInitialized) {
Modular . initialize ( AppModule ());
}
return handler
. use ( nestFrogMiddleware ( AppModule ()))
. use ( provider < String >((context) => message));
}
Important The nestFrogMiddleware must be added to make Nest services available in route handlers via Modular.of(context).
Route Handlers
Root Endpoint
Displays API information using injected services:
import 'package:dart_frog/dart_frog.dart' ;
import 'package:nest_frog_backend/modules/core_module.dart' ;
import 'package:nest_frog/nest_frog.dart' ;
Response onRequest ( RequestContext context) {
// Using the new Modular.of(context) API
final modular = Modular . of (context);
final welcomeMessage = modular. get < String >();
final logger = modular. get < LoggerService >();
final config = modular. get < ConfigService >();
logger. log ( 'Index route accessed' );
final response = {
'message' : welcomeMessage,
'app_name' : config.appName,
'version' : config.version,
'environment' : config.environment,
'database_type' : config.databaseType,
'port' : config.port,
'timestamp' : DateTime . now (). toIso8601String (),
};
return Response . json (body : response);
}
User List Endpoint
Handles GET and POST requests for users:
import 'package:dart_frog/dart_frog.dart' ;
import 'package:nest_frog_backend/modules/core_module.dart' ;
import 'package:nest_frog_backend/modules/user_module.dart' ;
import 'package:nest_frog/nest_frog.dart' ;
Future < Response > onRequest ( RequestContext context) async {
// Using familiar Modular API
final modular = Modular . of (context);
final userService = modular. get < UserService >();
final logger = modular. get < LoggerService >();
logger. log ( 'Users route accessed' );
switch (context.request.method) {
case HttpMethod . get :
return _getUsers (userService);
case HttpMethod .post :
return await _createUser (context, userService);
default :
return Response (statusCode : 405 , body : 'Method not allowed' );
}
}
Response _getUsers ( UserService userService) {
final users = userService. getAllUsers ();
return Response . json (
body : {
'users' : users. map ((user) => user. toJson ()). toList (),
'count' : users.length,
'timestamp' : DateTime . now (). toIso8601String (),
},
);
}
Future < Response > _createUser (
RequestContext context,
UserService userService,
) async {
try {
final body = await context.request. json () as Map < String , dynamic >;
final name = body[ 'name' ] as String ? ;
final email = body[ 'email' ] as String ? ;
if (name == null || email == null ) {
return Response . json (
statusCode : 400 ,
body : {
'error' : 'Missing required fields' ,
'message' : 'Both name and email are required' ,
},
);
}
final user = userService. createUser (name, email);
if (user == null ) {
return Response . json (
statusCode : 500 ,
body : {
'error' : 'Failed to create user' ,
'message' : 'An error occurred while creating the user' ,
},
);
}
return Response . json (
statusCode : 201 ,
body : {
'message' : 'User created successfully' ,
'user' : user. toJson (),
'timestamp' : DateTime . now (). toIso8601String (),
},
);
} catch (e) {
return Response . json (
statusCode : 400 ,
body : {
'error' : 'Invalid request body' ,
'message' : 'Request body must be valid JSON with name and email fields' ,
},
);
}
}
User Detail Endpoint
Handles getting a specific user by ID:
import 'package:dart_frog/dart_frog.dart' ;
import 'package:nest_frog_backend/modules/core_module.dart' ;
import 'package:nest_frog_backend/modules/user_module.dart' ;
import 'package:nest_frog/nest_frog.dart' ;
Response onRequest ( RequestContext context, String id) {
final modular = Modular . of (context);
final userService = modular. get < UserService >();
final logger = modular. get < LoggerService >();
logger. log ( 'User detail route accessed for id: $ id ' );
switch (context.request.method) {
case HttpMethod . get :
return _getUserById (userService, id);
default :
return Response (statusCode : 405 , body : 'Method not allowed' );
}
}
Response _getUserById ( UserService userService, String id) {
final userId = int . tryParse (id);
if (userId == null ) {
return Response . json (
statusCode : 400 ,
body : {
'error' : 'Invalid user ID' ,
'message' : 'User ID must be a valid integer' ,
},
);
}
final user = userService. getUserById (userId);
if (user == null ) {
return Response . json (
statusCode : 404 ,
body : {
'error' : 'User not found' ,
'message' : 'User with ID $ userId does not exist' ,
},
);
}
return Response . json (
body : {
'user' : user. toJson (),
'timestamp' : DateTime . now (). toIso8601String (),
},
);
}
API Endpoints
GET / - Welcome message
GET /users - List all users
POST /users - Create a user
GET /users/:id - Get user by ID
curl http://localhost:8080
# Response
{
"message" : "Welcome to Dart Frog with NestJS-like Modules!",
"app_name" : "Dart Frog Demo API",
"version" : "1.0.0",
"environment" : "development",
"database_type" : "in-memory",
"port" : 8080,
"timestamp" : "2026-03-03T12:00:00.000Z"
}
Key Concepts Demonstrated
Layered Architecture Separation of concerns with Repository (data), Service (business logic), and Controller (HTTP) layers.
Module Dependencies UserModule imports DatabaseModule and CoreModule to access their exported services.
Middleware Integration Nest modules integrate seamlessly with Dart Frog’s middleware system.
Type-Safe DI Services are injected with full type safety using Modular.of(context).get<T>().
Testing
The modular architecture makes testing straightforward:
import 'package:test/test.dart' ;
import 'package:nest_core/nest_core.dart' ;
void main () {
test ( 'UserService can be instantiated with dependencies' , () async {
// Initialize module
final module = UserModule ();
final locator = ModuleLocator ();
await module. registerProviders (locator);
// Get service
final userService = locator. get < UserService >();
expect (userService, isNotNull);
});
}
Next Steps