Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Root-level environment variables consumed by docker-compose.yml.
# Copy this file to `.env` in the project root before running `docker-compose up`.

# Secret used to sign JWT auth tokens. Generate a real one with:
# openssl rand -base64 48
# Never commit the real value.
JWT_SECRET=replace-with-a-long-random-secret
72 changes: 72 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: CI

on:
push:
branches: [main, master]
pull_request:
branches: [main, master]

jobs:
backend:
name: Backend (lint + test)
runs-on: ubuntu-latest
defaults:
run:
working-directory: backend
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: backend/package-lock.json

- name: Install dependencies
run: npm ci

- name: Lint
run: npm run lint

- name: Test
run: npm test

frontend:
name: Frontend (lint + typecheck + build)
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json

- name: Install dependencies
run: npm ci

- name: Lint
run: npm run lint

- name: Typecheck
run: npm run typecheck

- name: Build
run: npm run build

docker-build:
name: Validate Docker images build
runs-on: ubuntu-latest
needs: [backend, frontend]
steps:
- uses: actions/checkout@v4

- name: Build backend image
run: docker build -t todo-backend:ci ./backend

- name: Build frontend image
run: docker build -t todo-frontend:ci ./frontend --build-arg VITE_API_URL=http://localhost:5000
169 changes: 134 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,69 +1,168 @@
# 🐋 Docker To-Do List - Fullstack Application
# 🐋 Docker To-Do List Fullstack Application

![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white)
![React](https://img.shields.io/badge/react-%2320232a.svg?style=for-the-badge&logo=react&logoColor=%2361DAFB)
![Node.js](https://img.shields.io/badge/node.js-6DA55F?style=for-the-badge&logo=node.js&logoColor=white)
![TailwindCSS](https://img.shields.io/badge/tailwindcss-%2338B2AC.svg?style=for-the-badge&logo=tailwind-css&logoColor=white)
![MongoDB](https://img.shields.io/badge/MongoDB-%234ea94b.svg?style=for-the-badge&logo=mongodb&logoColor=white)

### This project is a complete to-do list application built to demonstrate the power of **containerization** and **orchestration** of microservices.

### The application uses a modern frontend in React, a robust API in Node.js, a NoSQL database, and an automated CI/CD pipeline.
A full-stack to-do list application with real JWT authentication, a MongoDB-backed
REST API, and a Dockerized development/production setup.

## Features

- ✅ Create, list, and remove tasks.
- ✅ Mark tasks as completed.
- ✅ Data persistence with MongoDB.
- ✅ Responsive interface with TailwindCSS.
- ✅ Development environment identical to production via Docker.
- ✅ Automatic tests integrated into the workflow.
- User registration and login with JWT (`jsonwebtoken` + `bcryptjs` password hashing).
- Create, list, edit, complete, delete, and drag-and-drop reorder tasks — reordering
is persisted on the server (`PATCH /todos/reorder`), not just visual.
- Optional due dates per task.
- Data persistence with MongoDB.
- Responsive interface with TailwindCSS + shadcn/ui components.
- Backend health check endpoint (`GET /health`) and Docker `HEALTHCHECK`.
- Automated CI (GitHub Actions): backend lint + tests, frontend lint + typecheck +
build, and a Docker image build check.

## Technologies Used

- **Frontend:** React, Vite, TailwindCSS.
- **Backend:** Node.js, Express, Mongoose.
- **Frontend:** React 19, Vite, TypeScript, TailwindCSS, shadcn/ui, @dnd-kit.
- **Backend:** Node.js, Express, Mongoose, JWT, bcryptjs.
- **Database:** MongoDB.
- **Infrastructure:** Docker, Docker Compose.
- **DevOps:** GitHub Actions (CI).
- **Testing:** Jest + Supertest (backend), with Mongoose models mocked so the suite
runs deterministically without a live database connection.

## How to Run
## Project Structure

You don't need to install Node or MongoDB on your machine, just **Docker** and **Docker Compose**.
```
backend/
src/
config/ # env loading + Mongo connection
models/ # Mongoose schemas (User, Todo)
middlewares/ # auth guard, centralized error handler
controllers/ # route handlers
routes/ # Express routers (auth, todos, health)
app.js # Express app factory (used by both server and tests)
index.js # process entrypoint (validates env, connects DB, starts server)
tests/
unit/ # pure validation logic
integration/ # HTTP-level tests via supertest, with mocked Mongoose models
frontend/
src/ # React app (Vite + TypeScript)
docker-compose.yml # db, api, web, tests services
.github/workflows/ci.yml
```

1. Clone the repository:
## How to Run (Docker)

```bash
git clone https://github.com/RobotEby/journey-in-backend.git
cd docker-todo-list
```
You need **Docker** and **Docker Compose**. No local Node or MongoDB install required.

2. Start the application:
1. Copy the environment template and set a real JWT secret:

```bash
docker-compose up --build
```
```bash
cp .env.example .env
# Edit .env and replace JWT_SECRET with the output of:
openssl rand -base64 48
```

2. Start the stack:

3. Access the application in your browser:
```bash
docker-compose up --build
```

3. Access the application:

- Frontend: http://localhost:8080
- Backend API: http://localhost:5000
- Backend health check: http://localhost:5000/health

## How to Run (local, without Docker)

Requires Node.js 20+ and a running MongoDB instance.

```bash
Frontend: http://localhost:5173
Backend API: http://localhost:5000/todos
# Backend
cd backend
cp .env.example .env # set JWT_SECRET and MONGO_URI
npm install
npm run dev # http://localhost:5000

# Frontend (separate terminal)
cd frontend
npm install
npm run dev # http://localhost:8080
```

## Testing

Tests are run automatically with each push to the repository via GitHub Actions. To run the tests locally via Docker:
Backend tests are unit/integration tests written with Jest and Supertest. The
integration tests exercise the real Express routes and middleware, but the
Mongoose models are mocked with deterministic fixtures — **they do not require
a running MongoDB instance** and do not hit any external service.

```bash
# Locally
cd backend
npm test

# Via Docker Compose
docker-compose run --rm tests
```

## Project Structure

- `/frontend:` React application configured with Vite and Tailwind.
- `/backend:` Node.js API connected to MongoDB.
- `docker-compose.yml:` Definition and orchestration of services (db, api, web, tests).
- `.github/workflows:` Continuous integration pipeline configuration.

#### Developed with 🐋 by [RobotEby](https://github.com/RobotEby)
There is currently **no automated frontend test suite**. Frontend correctness is
covered by `npm run lint`, `npm run typecheck`, and `npm run build` in CI.

## Backend Scripts

| Command | Description |
| --- | --- |
| `npm run dev` | Start with nodemon (auto-restart) |
| `npm start` | Start once, no auto-restart |
| `npm run lint` | ESLint |
| `npm test` | Jest test suite |
| `npm run test:coverage` | Jest with coverage report |

## Frontend Scripts

| Command | Description |
| --- | --- |
| `npm run dev` | Vite dev server |
| `npm run build` | Production build |
| `npm run lint` | ESLint |
| `npm run typecheck` | `tsc --noEmit` |

## API Overview

| Method | Route | Auth | Description |
| --- | --- | --- | --- |
| POST | `/auth/register` | No | Create a user account |
| POST | `/auth/login` | No | Log in and receive a JWT |
| GET | `/auth/me` | Yes | Return the authenticated user |
| GET | `/todos` | Yes | List the user's tasks |
| POST | `/todos` | Yes | Create a task |
| PUT | `/todos/:id` | Yes | Toggle `completed` |
| PATCH | `/todos/:id` | Yes | Edit `text` and/or `dueDate` |
| PATCH | `/todos/reorder` | Yes | Persist a new task order (`{ orderedIds: string[] }`) |
| DELETE | `/todos/:id` | Yes | Delete a task |
| GET | `/health` | No | Liveness/readiness check |

All `/todos` routes are scoped to the authenticated user's own tasks.

## Known Limitations

- No password-reset flow.
- No rate limiting on `/auth/login` or `/auth/register` (recommended before any
public deployment).
- No end-to-end/browser test suite — only backend unit/integration tests exist.
- The production frontend bundle is a single ~260 KB gzip chunk; code-splitting
was intentionally left out of scope for this pass.

## Security Notes

- `JWT_SECRET` **must** be provided via environment variable; the application
refuses to start without it outside of the test environment.
- If you obtained this repository from a version where a real secret value was
committed to `docker-compose.yml` or `backend/src/index.js`, treat that value
as compromised and rotate/replace it — do not reuse it.

#### Developed by [RobotEby](https://github.com/RobotEby)
14 changes: 14 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Copy this file to `.env` for local (non-Docker) backend development.
# Never commit the real .env file or real secret values.

PORT=5000
MONGO_URI=mongodb://localhost:27017/todolist

# Generate with: openssl rand -base64 48
JWT_SECRET=replace-with-a-long-random-secret

JWT_EXPIRES_IN=7d
JWT_EXPIRES_IN_REMEMBER=30d

# Comma-separated list of allowed frontend origins for CORS.
CORS_ORIGINS=http://localhost:8080,http://127.0.0.1:8080
17 changes: 17 additions & 0 deletions backend/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"root": true,
"env": {
"node": true,
"es2022": true,
"jest": true
},
"parserOptions": {
"ecmaVersion": 2022,
"sourceType": "module"
},
"extends": ["eslint:recommended"],
"rules": {
"no-unused-vars": ["warn", { "argsIgnorePattern": "^_|^next$" }]
},
"ignorePatterns": ["node_modules", "coverage"]
}
9 changes: 7 additions & 2 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
FROM node:18-alpine

WORKDIR /app
# WORKDIR must match the bind mount target in docker-compose.yml
# (./backend:/app/backend) so local edits are reflected inside the container.
WORKDIR /app/backend

COPY package*.json ./
RUN npm install
RUN npm ci

COPY . .

EXPOSE 5000

HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=5 \
CMD node -e "fetch('http://localhost:5000/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"

CMD ["npm", "start"]
7 changes: 7 additions & 0 deletions backend/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** @type {import('jest').Config} */
export default {
testEnvironment: 'node',
transform: {},
testMatch: ['**/tests/**/*.test.js'],
coveragePathIgnorePatterns: ['/node_modules/'],
};
Loading
Loading