A modern RESTful Task Management API built with Node.js, Express.js, PostgreSQL, Docker, Supabase Auth, and JWT.
Task API evolved from a basic CRUD exercise into a persistent, containerized, authenticated backend.
| Stage | Evolution |
|---|---|
| A1 | In-memory REST CRUD API |
| A2 | SQLite persistence |
| A3 | PostgreSQL + Docker Compose |
| A4 | Supabase Auth + JWT-protected routes |
- π Supabase email/password authentication
- ποΈ JWT bearer authorization
- π‘οΈ Reusable
requireAuthmiddleware - π¦ Protected task CRUD operations
- π PostgreSQL persistence
- π³ Docker + Docker Compose
- πΎ Persistent Docker volume
- π§· Parameterized SQL queries
- β€οΈ Real database health check
- π Swagger UI with Authorize support
- βοΈ
.env-based configuration
Client / Swagger UI
β
βββ signup / login βββββββΊ Supabase Auth
β β
β JWT access token
β β
βββββββββββββββββββββββββββββββββ
β
β Authorization: Bearer <JWT>
βΌ
ββββββββββββββββββββββββββ
β Node.js + Express API β
β Port 3000 β
βββββββββββββ¬βββββββββββββ
β
requireAuth
β
βΌ
ββββββββββββββββββββββββββ
β Protected Task Routes β
βββββββββββββ¬βββββββββββββ
β parameterized SQL
βΌ
ββββββββββββββββββββββββββ
β PostgreSQL Container β
βββββββββββββ¬βββββββββββββ
β
βΌ
Docker Volume
taskdata
The API uses Supabase Auth for user authentication. After login, Supabase returns a JWT access token.
Protected requests use:
Authorization: Bearer <ACCESS_TOKEN>Without a valid token, protected routes return 401 Unauthorized.
Email + Password
β
POST /auth/login
β
Supabase Auth
β
JWT access_token
β
Bearer Authorization
β
requireAuth middleware
β
Protected API
Security: Never commit
.env, passwords, Supabase secrets, or real JWT access tokens.
| Method | Endpoint | Access | Purpose |
|---|---|---|---|
POST |
/auth/signup |
π Public | Create account |
POST |
/auth/login |
π Public | Login and receive JWT |
POST |
/auth/logout |
π Protected | Logout |
GET |
/ |
π Public | API information |
GET |
/health |
π Public | API + DB health |
GET |
/tasks |
π Protected | Get all tasks |
GET |
/tasks/:id |
π Protected | Get task by ID |
POST |
/tasks |
π Protected | Create task |
PUT |
/tasks/:id |
π Protected | Update task |
DELETE |
/tasks/:id |
π Protected | Delete task |
git clone https://github.com/100rabheimer/task-api.git
cd task-apiCopy .env.example to .env and add your own values:
DATABASE_URL=postgres://postgres:your_password@db:5432/tasks
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_KEY=your_supabase_anon_or_publishable_key
PORT=3000docker compose up --buildThen open:
| Resource | Local URL |
|---|---|
| API | http://localhost:3000 |
| Health | http://localhost:3000/health |
| Swagger UI | http://localhost:3000/docs |
Stop the stack with:
docker compose downSchema:
CREATE TABLE IF NOT EXISTS tasks (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
done BOOLEAN NOT NULL DEFAULT FALSE
);Example task:
{
"id": 1,
"title": "Learn Express",
"done": false
}The app seeds initial records only when the table is empty.
POST /auth/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "StrongPassword123!"
}Then use the returned access_token:
GET /tasks
Authorization: Bearer <ACCESS_TOKEN>$login = Invoke-RestMethod `
-Uri "http://localhost:3000/auth/login" `
-Method Post `
-ContentType "application/json" `
-Body '{"email":"user@example.com","password":"StrongPassword123!"}'
$token = $login.access_token
Invoke-RestMethod `
-Uri "http://localhost:3000/tasks" `
-Method Get `
-Headers @{ Authorization = "Bearer $token" }Open:
http://localhost:3000/docs
For protected endpoints:
- Run
POST /auth/login. - Copy the returned
access_token. - Click Authorize π.
- Paste the JWT.
- Run any protected
/tasksendpoint.
Swagger sends the bearer token automatically after authorization.
Crop or hide JWT values before adding Swagger screenshots to a public repository.
GET /health executes:
SELECT 1;Expected response:
{
"status": "ok",
"database": "connected"
}This verifies both the API and PostgreSQL connection.
task-api/
β
βββ assets/
β βββ postgres-database.png
β
βββ authMiddleware.js
βββ supabaseClient.js
βββ db.js
βββ index.js
βββ openapi.json
β
βββ Dockerfile
βββ compose.yaml
βββ .dockerignore
βββ .env.example
βββ .gitignore
β
βββ package.json
βββ package-lock.json
βββ README.md
Inspect PostgreSQL:
docker compose exec db psql -U postgres -d tasksInside psql:
\dt
SELECT * FROM tasks;- β Signup and login
- β JWT access token returned
- β Protected routes reject missing authorization
- β Valid JWT grants task access
- β Swagger Bearer authorization works
- β PostgreSQL CRUD works
- β Parameterized queries
- β Database health check
- β Docker Compose startup
- β Persistent database volume
- β
.envkept outside Git
Backend: REST design, Express middleware, CRUD, validation, HTTP status codes.
Database: PostgreSQL, node-postgres, parameterized queries, initialization, persistence.
Security: Supabase Auth, JWTs, bearer authorization, protected routes, environment secrets.
DevOps: Docker, Docker Compose, container networking, volumes, reproducible local setup.
Documentation: OpenAPI 3 and Swagger UI with authenticated endpoint testing.
.envis Git-ignored..env.examplecontains placeholders only.- Password authentication is handled by Supabase.
- No custom password hashing is implemented in this API.
- SQL values use parameterized queries.
- Protected endpoints require bearer authentication.
- Real JWTs and credentials must not appear in public screenshots.
