ToDoList API REST API for managing users and to-do lists, with JWT‑based authentication, PostgreSQL persistence (via Docker), and password hashing using bcrypt.
This project is designed as a solid backend foundation, following good practices in project structure, authentication, authorization, and error handling.
Technologies Node.js (Express 5)
PostgreSQL (Docker + Docker Compose)
JWT (JSON Web Token) for authentication
bcrypt for password hashing
dotenv for environment variables
Prerequisites Before running the project, make sure you have:
Node.js (v18+ recommended)
npm or yarn
Docker and Docker Compose
Git (optional, if cloning the repository)
Getting started
- Clone the repository
git clone https://github.com/Revoltzin/ToDoList-API cd YOUR_REPOSITORY
- Configure PostgreSQL with Docker Create a docker-compose.yml file in the project root (if you don’t already have one):
version: '3.8'
services: postgres: image: postgres:16 container_name: postgres_app environment: POSTGRES_DB: usersdb POSTGRES_USER: postgres POSTGRES_PASSWORD: 123456 ports: - "5433:5432" volumes: - postgres_data:/var/lib/postgresql/data restart: unless-stopped
volumes: postgres_data:
Start the PostgreSQL container:
docker-compose up -d postgres
- Configure environment variables
Create a .env file in the project root:
DB_HOST=localhost DB_PORT=5433 DB_NAME=usersdb DB_USER=postgres DB_PASSWORD=123456
JWT_SECRET=your_very_secure_secret_key_here
Adjust values as needed for your environment.
- Install dependencies
npm install
yarn install
- Create the database tables Connect to the database inside the PostgreSQL container:
docker exec -it postgres_app psql -U postgres -d usersdb Then run:
CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, email VARCHAR(255) UNIQUE NOT NULL, password VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
CREATE TABLE IF NOT EXISTS todolists ( id SERIAL PRIMARY KEY, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, title VARCHAR(255) NOT NULL, description TEXT, is_done BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
\q
- Start the server
npm start
npm run dev
The API should now be running at: http://localhost:3000
Project structure
src/ app/ Controller/ UsersController.js ListsController.js Repository/ UsersRepository.js ToDoListsRepository.js middlewares/ auth.js database/ index.js routes/ users.routes.js todolists.routes.js utils/ asyncHandler.js app.js docker-compose.yml .env package.json
This structure separates concerns:
Controller: HTTP layer (request/response handling).
Repository: Data access (PostgreSQL queries).
middlewares: Authentication and other cross‑cutting middleware.
utils: Helpers such as async error handling.
database: Database connection pool configuration.
API endpoints Authentication & Users Register user Method: POST
URL: /users
Request body (JSON):
json { "name": "Your Name", "email": "you@example.com", "password": "your_password" } Response (201 Created): Returns the created user (password is stored hashed in the database).
Login Method: POST
URL: /users/login
Request body (JSON):
json { "email": "you@example.com", "password": "your_password" } Response (200 OK):
json { "token": "JWT_TOKEN_HERE" } You will use this token in the Authorization header for protected routes.
List users (optional, if implemented) Method: GET
URL: /users
Depending on your implementation, this endpoint may be protected by JWT.
ToDo Lists (protected routes) All to‑do list routes require authentication.
Required header:
Key: Authorization
Value: Bearer YOUR_JWT_TOKEN
The userId is extracted from the token and used to ensure that each user only accesses their own lists.
Get all lists for the authenticated user Method: GET
URL: /todolists
Response (200 OK) example:
json [ { "id": 1, "title": "My first task", "description": "just a test", "is_done": false, "created_at": "2025-12-10T20:00:00.000Z" } ] Create a new to‑do list Method: POST
URL: /todolists
Request body (JSON):
json { "title": "Study Node.js", "description": "Review JWT authentication" } Response (201 Created):
json { "id": 1, "user_id": 1, "title": "Study Node.js", "description": "Review JWT authentication", "is_done": false, "created_at": "2025-12-10T20:00:00.000Z" } Update a to‑do list Method: PUT
URL: /todolists/:id
Example: PUT /todolists/1
Request body (JSON):
json { "title": "Study Node.js (updated)", "description": "Review JWT and middleware", "is_done": true } Response (200 OK): Returns the updated to‑do list, or 404 Not Found if the list does not belong to the authenticated user or does not exist.
Delete a to‑do list Method: DELETE
URL: /todolists/:id
Example: DELETE /todolists/1
Response (204 No Content): Empty body if deletion succeeds, or 404 Not Found if the list does not belong to the authenticated user or does not exist.
Authentication & authorization Login returns a JWT whose payload includes at least userId (and optionally email).
The authentication middleware:
Reads the Authorization header in the Bearer TOKEN format.
Verifies the token using jwt.verify and JWT_SECRET.
On success, attaches the decoded payload to req.user.
If there is no token → responds 401 Token required.
If token is invalid or expired → responds 403 Invalid or expired token.
To‑do list endpoints always rely on req.user.userId so users can only access their own data.
Error handling Controllers are implemented as async functions.
Routes wrap controllers using an asyncHandler helper to forward async errors to the global error handler.
Global error middleware (example):
js app.use((err, req, res, next) => { console.error(err); return res.status(500).json({ error: 'Internal server error' }); }); You can extend this to handle validation errors, database errors, etc., with more specific status codes and messages.
Testing with Insomnia/Postman Suggested flow:
Register a user
POST /users with JSON body { name, email, password }.
Login
POST /users/login and copy the token from the response.
Use protected routes
Add header Authorization: Bearer YOUR_TOKEN to all /todolists requests.
Test:
POST /todolists to create a task.
GET /todolists to list tasks.
PUT /todolists/:id to update a task.
DELETE /todolists/:id to remove a task.
thats it, thanks and enjoy the project.