Build a Notes CRUD API with Globe Database and Dart Frog
Deploy a Dart backend with a serverless SQLite database in under 10 minutes using Dart Frog and Drift.
Using this guide, you’ll build and deploy a CRUD API using Globe’s serverless SQLite database, Dart Frog, and Drift. It covers database setup, schema definition, endpoints creation, and deployment to Globe’s global network.
10 min read
Features Covered
- Creating a new Globe Database instance from the dashboard
- Defining a database schema with Drift
- Building a full CRUD API with Dart Frog.
- Testing the API locally and live with
curl
- Deploying a data-driven app to Globe.
Prerequisites
- Dart SDK Installed: If you have Flutter installed, the Dart SDK is already included. If not, Install Dart.
- Globe Account: You'll need an account to deploy projects. Sign up or log in to Globe.
- Globe CLI Installed and Authenticated: Install the CLI by running
dart pub global activate globe_cli
and log in usingglobe login
.
Step 1: Create Your Globe Database
First, create the database on the Globe dashboard.
- Navigate to the Globe dashboard
- Go to the Databases tab and click Create Database
- Select a location, click Create, and a name will be automatically assigned
- Copy the auto-generated database name (e.g.,
gold-butterfly-1234
)
Step 2: Set Up the Dart Frog Project
Create a new Dart Frog project and add the necessary Drift and SQLite dependencies.
-
In your terminal, run the following commands:
dart_frog create my_notes_api cd my_notes_api dart pub add drift sqlite3 dart pub add --dev drift_dev build_runner
-
Now, open the newly created
my_notes_api
folder in your favorite code editor.
Step 3: Define the Database and Tables
Create a new file at lib/database.dart
to define your schema with Drift. Remember to replace your-db-name
with the name of the database you copied in Step 1.
import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:sqlite3/sqlite3.dart';
part 'database.g.dart';
// Defines the 'notes' table.
class Notes extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get title => text()();
TextColumn get content => text()();
}
@DriftDatabase(tables: [Notes])
class AppDatabase extends _$AppDatabase {
AppDatabase() : super(_openConnection());
@override
int get schemaVersion => 1;
static QueryExecutor _openConnection() {
// Before deployment, replace this name with the Globe live database.
return NativeDatabase.opened(sqlite3.open('your-db-name.db'));
}
}
Analyzer errors will be present in database.dart
until the necessary helper
code is generated in the next step.
Step 4: Generate the Drift Code
Run the build_runner
to generate the database.g.dart
file.
dart run build_runner build --delete-conflicting-outputs
Step 5: Write the API Logic
We will create the route files to handle all CRUD operations for our "notes" table.
A. Provide the Database and Handle GET
/ POST
This route will provide the database to all routes below it, handle GET
requests to fetch all notes, and POST
requests to create a new one.
-
Create a new file at
routes/notes/_middleware.dart
and add the following:import 'package:dart_frog/dart_frog.dart'; import '../../lib/database.dart'; final _db = AppDatabase(); Handler middleware(Handler handler) { return handler.use(provider<AppDatabase>((_) => _db)); }
-
Create a file at
routes/notes/index.dart
and add this code:import 'dart:io'; import 'package:dart_frog/dart_frog.dart'; import '../../lib/database.dart'; Future<Response> onRequest(RequestContext context) { return switch (context.request.method) { HttpMethod.get => _getNotes(context), HttpMethod.post => _createNote(context), _ => Future.value(Response(statusCode: HttpStatus.methodNotAllowed)), }; } Future<Response> _getNotes(RequestContext context) async { final db = context.read<AppDatabase>(); final allNotes = await db.select(db.notes).get(); return Response.json( body: allNotes.map((note) => note.toJson()).toList(), ); } Future<Response> _createNote(RequestContext context) async { final db = context.read<AppDatabase>(); final body = await context.request.json() as Map<String, dynamic>; final newNote = await db.into(db.notes).insertReturning( NotesCompanion.insert( title: body['title'] as String, content: body['content'] as String, ), ); return Response.json( statusCode: HttpStatus.created, body: newNote.toJson(), ); }
B. Handle GET
(one), PATCH
, and DELETE
This dynamic route handles operations for a specific note.
-
Create a file at
routes/notes/[id].dart
and add this code:import 'dart:io'; import 'package:dart_frog/dart_frog.dart'; import 'package:drift/drift.dart'; import '../../lib/database.dart'; Future<Response> onRequest(RequestContext context, String id) { return switch (context.request.method) { HttpMethod.get => _getNoteById(context, id), HttpMethod.patch => _updateNote(context, id), HttpMethod.delete => _deleteNote(context, id), _ => Future.value(Response(statusCode: HttpStatus.methodNotAllowed)), }; } Future<Response> _getNoteById(RequestContext context, String id) async { final db = context.read<AppDatabase>(); final noteId = int.parse(id); final note = await (db.select(db.notes)..where((t) => t.id.equals(noteId))) .getSingleOrNull(); return note == null ? Response(statusCode: HttpStatus.notFound) : Response.json(body: note.toJson()); } Future<Response> _updateNote(RequestContext context, String id) async { final db = context.read<AppDatabase>(); final noteId = int.parse(id); final body = await context.request.json() as Map<String, dynamic>; final success = await (db.update(db.notes)..where((t) => t.id.equals(noteId))).write( NotesCompanion( content: Value(body['content'] as String), ), ); return success > 0 ? Response(statusCode: HttpStatus.noContent) : Response(statusCode: HttpStatus.notFound); } Future<Response> _deleteNote(RequestContext context, String id) async { final db = context.read<AppDatabase>(); final noteId = int.parse(id); final success = await (db.delete(db.notes)..where((t) => t.id.equals(noteId))).go(); return success > 0 ? Response(statusCode: HttpStatus.noContent) : Response(statusCode: HttpStatus.notFound); }
Step 6: Test Your API Locally
Run your server to ensure all endpoints work.
-
Start the development server:
dart_frog dev --port 8080
-
In a new terminal window, use
curl
to test.-
Create a note:
curl -X POST -H "Content-Type: application/json" \ -d '{"title": "My First Note", "content": "Hello Globe DB!"}' \ http://localhost:8080/notes
-
List all notes:
curl http://localhost:8080/notes
-
Step 7: Deploy to Globe
Deploy the API to the Globe platform.
globe deploy
The Globe CLI will guide you through linking the project and will automatically connect your database. Once complete, you will receive a unique URL for your live API.
Step 8: Test the Live API
After deployment, test the live API endpoints using curl
and your new URL.
-
Create a note on your live API:
curl -X POST https://<YOUR_LIVE_URL>/notes \ -H "Content-Type: application/json" \ -d '{"title": "Live Note", "content": "This is on Globe!"}'
-
Get the list of live notes:
curl https://<YOUR_LIVE_URL>/notes
What's Next
- Explore a Full-Stack Example: See Globe DB in a more advanced, real-world application by checking out Recall, a complete note-taking PWA we built to showcase its capabilities.
- We want your feedback! Globe DB is in Beta, and your experience is crucial. Please share your thoughts and questions with us on Discord.
Couldn't find the guide you need? Talk to us in Discord