This content originally appeared on DEV Community and was authored by Samuel Adekunle
Hey, Flutter fam! Episode 4 of our Flutter x Serverpod series is here! Last time we integrated authentication: login, registration, reset/forgot password, validation code, and secure Home screen. If you're signed in and staring at an empty task list right now… perfect. Today, we're filling it with secure CRUD operations for our fintech to-do app.
We're creating, reading, updating, and deleting trade alerts that are fully owned by the logged-in user, with validation to prevent anyone from entering a negative amount. By the end, your app will feel production-ready, complete with empty states, loading spinners, error toasts, and more. Let's build! Subscribe and let's code! 🚀
FOR BETTER UNDERSTANDING, REFER TO THE YOUTUBE VIDEO.
First, our Task model
In fintech_todo_server/lib/src/task.spy.yaml:
class: Task
table: task
fields:
id: int?
title: String
description: String
amount: double
dueDate: DateTime?
userId: int
Create migration:
cd fintech_todo_server
serverpod create-migration
Apply
dart run bin/main.dart --role maintenance --apply-migrations
Done - Postgres now has a task table linked to the auth user table.
Building the Secure TaskEndpoint
File: fintech_todo_server/lib/src/task_endpoint.dart
import 'package:fintech_todo_server/src/exceptions.dart';
import 'package:fintech_todo_server/src/generated/protocol.dart';
import 'package:serverpod/serverpod.dart';
class TaskEndpoint extends Endpoint {
@override
bool get requireLogin => true;
// CREATE
Future<Task> createTask(Session session, Task request) async {
final auth = await session.authenticated;
if (auth == null) {
throw AuthorizationException(message: "You're not authenticated!");
}
final userId = auth.userId;
// Validation
if (request.title.isEmpty) {
throw ValidationException(message: "Title cannot be empty");
}
if (request.amount <= 0) {
throw ValidationException(message: "Amount must be greater than zero");
}
final task = Task(
userId: userId,
title: request.title,
amount: request.amount,
description: request.description,
dueDate: request.dueDate,
);
final inserted = await Task.db.insertRow(session, task);
return inserted;
}
// READ
Future<List<Task>> getTasks(Session session) async {
final auth = await session.authenticated;
if (auth == null) {
throw AuthorizationException(message: "You're not authenticated!");
}
final userId = auth.userId;
final tasks = await Task.db.find(
session,
where: (t) => t.userId.equals(userId),
orderBy: (t) => t.dueDate,
orderDescending: true,
);
return tasks;
}
// UPDATE
Future<Task> updateTask(Session session, Task request) async {
final auth = await session.authenticated;
if (auth == null) {
throw AuthorizationException(message: "You're not authenticated!");
}
final userId = auth.userId;
if (request.id == null) {
throw ValidationException(message: "Task ID is required for update");
}
if (request.title.isEmpty) {
throw ValidationException(message: "Title cannot be empty");
}
if (request.amount <= 0) {
throw ValidationException(message: "Amount must be greater than zero");
}
final existingTask = await Task.db.findById(session, request.id!);
if (existingTask == null) {
throw NotFoundException(message: "Task with ID ${request.id} not found.");
}
if (existingTask.userId != userId) {
throw AuthorizationException(
message: "You do not have permission to update this task.");
}
final updatedTask = existingTask.copyWith(
title: request.title,
amount: request.amount,
description: request.description,
dueDate: request.dueDate,
);
final result = await Task.db.updateRow(session, updatedTask);
return result;
}
// DELETE
Future<void> deleteTask(Session session, int taskId) async {
final auth = await session.authenticated;
if (auth == null) {
throw AuthorizationException(message: "You're not authenticated!");
}
final userId = auth.userId;
final rowsDeleted = await Task.db.deleteWhere(
session,
where: (t) => t.id.equals(taskId) & t.userId.equals(userId),
);
if (rowsDeleted.isEmpty) {
throw NotFoundException(message: "Task with ID $taskId not found.");
}
}
}
Regenerate client:
serverpod generate
Flutter Side: Beautiful Task List + Forms
Switch to fintech_todo_flutter
Key screens we built live:
TaskListScreen - Empty state + ListView.builder
TaskFormDialog - Create/Edit modal
Delete confirmation
Code highlights (copy-paste ready in description):
// In HomeScreen
Future<void> _loadTasks() async {
try {
final tasks = await client.task.getTasks();
setState(() => taskList = tasks);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Load failed: $e')));
}
}
// FAB → showDialog(TaskFormDialog())
Real demo flow:
Login → Empty state ("No trades yet - add one!")
Tap FAB → Fill form → Save → Task appears instantly
Edit → Update → List refreshes
Delete → Confirmation → Gone
That's it - you now have a fully secure, user-owned task system! No more public todos - every task belongs to the signed-in trader.
I hope you have learn something incredible. Press that follow button if you're not following me yet. Also, make sure to subscribe to the newsletter so you're notified when I publish a new article. Kindly press the clap button as many times as you want if you enjoy it, and feel free to ask a question.
Source Code 👇 - Show some ❤️ by starring ⭐ the repo and follow me 😄!
techwithsam
/
fintech_todo_serverpod
Full-Stack Mobile Development (Flutter + Serverpod) - TechWithSam
Full-Stack with Flutter x Serverpod | Tech With Sam
Overview
Top question on Stack Overflow this year: 'How do I build a backend without leaving Dart?' Enter Serverpod—the open-source powerhouse that's making full-stack Dart the 2025 must-have. In this new series, we're building a real-world fintech to-do app from scratch: secure tasks, real-time updates, and cloud-ready deploys. No more half-solutions!
[Course] Full-Stack Mobile Development With Flutter and Serverpod - Watch on youtube
Project layout
- fintech_todo_server/ — Serverpod server
- bin/main.dart — server entrypoint
- Dockerfile — for containerized deploys
- migrations/ — DB migrations
- lib/src/generated/ — generated protocol & endpoints
- fintech_todo_flutter/ — Flutter client
- lib/main.dart — app entrypoint
- fintech_todo_client/ — generated client package
Quick start
Prereqs: Docker, Flutter, Dart SDK (for server).
-
Clone:
git clone https://github.com/techwithsam/fintech_todo_serverpod cd fintech_todo -
Start local DB & Redis (example using docker-compose):
cd fintech_todo_server && docker compose up -d
-
Run the server:
# from fintech_todo/ dart pub get dart…
Drop a like if this helped, comment your experience with Serverpod and this series, and join Discord for the updated repo + bonus snippets: https://discord.gg/NytgTkyw3R
Samuel Adekunle, Tech With Sam YouTube | Part 5Teaser
Happy Building! 🥰👨💻
This content originally appeared on DEV Community and was authored by Samuel Adekunle
Samuel Adekunle | Sciencx (2025-11-22T07:00:00+00:00) Full-Stack Mobile Development (Flutter + Serverpod) #4 - Task CRUD Operations. Retrieved from https://www.scien.cx/2025/11/22/full-stack-mobile-development-flutter-serverpod-4-task-crud-operations/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.