Dart Backend REST API with JWT Auth
Build a REST API server in Dart with the shelf framework and PostgreSQL, with user accounts and secure login. Users register and log in to receive a JSON Web Token (JWT), which they send with every request; middleware checks the token and the user's role (user or admin) before letting a request through. The API manages a resource, such as tasks or articles, where users can only change their own items and admins can change everything. Passwords are hashed, input is validated, errors have a consistent format, and requests are logged and rate limited. You'll learn server-side Dart, composing shelf middleware, working with PostgreSQL through the postgres package, JWT authentication and authorization, and testing an API.
- Estimate
- ~14h
- Steps
- 7
- Completed by
- 0
- Proposed by
- codeseed.app
shelf · postgres package · dart_jsonwebtoken
Project roadmap
- 01
Set up the server
~1.5hCreate a project with dart create -t server-shelf, and add shelf_router, postgres and dart_jsonwebtoken. Build the pipeline with Pipeline().addMiddleware(...).addHandler(router), and a GET /health endpoint. Read settings (port, database URL, JWT secret) from environment variables, refusing to start when the secret is missing. Done when: the server starts, and /health returns 200.
- 02
Connect to PostgreSQL
~2hConnect with the postgres package using a connection pool. Write SQL migration files for users (id, email unique, password hash, role, created at) and tasks (id, owner, title, done, created at), and a small runner that applies unapplied migrations in order and records them in a table. Always use parameterized queries. Done when: starting the server creates the tables on an empty database.
- 03
Register and log in
~2hAdd POST /auth/register (validate the email and a password of at least 10 characters, hash it with a slow algorithm such as bcrypt or argon2 from pub.dev) and POST /auth/login, which checks the password and returns a JWT signed with HS256 containing the user id and role, expiring after 1 hour. Give the same error for a wrong email or password. Done when: a user can register and log in, and receives a token.
- 04
Add authentication middleware
~2hWrite middleware that reads the Authorization: Bearer header, verifies the token's signature and expiry with dart_jsonwebtoken, and adds the user to the request context, returning 401 when it's missing or invalid. Write a requireRole('admin') middleware returning 403 for other users. Done when: protected routes reject missing and expired tokens, and admin routes reject normal users.
- 05
Build the protected endpoints
~2.5hAdd CRUD endpoints for tasks: users list, create, update and delete only their own tasks (return 404, not 403, for other users' tasks, so their existence isn't revealed), while admins can see and manage all tasks. Validate the input, support pagination with limit and offset, and return errors in one JSON format. Done when: ownership rules hold for every endpoint.
- 06
Add logging, errors and rate limiting
~2hAdd middleware for request logging (method, path, status and duration, never tokens or passwords), a global error handler that turns exceptions into a 500 JSON response without internal details, and a rate limiter on the login endpoint, for example 5 attempts per minute per IP, returning 429. Done when: logs show every request, and the sixth quick login attempt gets 429.
- 07
Test the API
~2hWrite tests with package:test that start the server against a test database (for example PostgreSQL in Docker): registering, logging in, token checks (missing, expired, tampered), ownership rules for users and admins, validation errors and rate limiting. Done when: dart test passes.
Resources
Ready to build this?
Get a GitHub repo and start building. Your AI reviewer checks each step as you go.
Tech stack