Skip to main content
TaskFlow Pro is two separate applications that communicate over HTTP. The Next.js frontend runs on port 3000 and calls a JSON REST API served by Express on port 3001. Both applications share no code — the boundary is the API contract.

System diagram

┌─────────────────────────────────────────────────────────┐
│                        Browser                          │
└───────────────────────┬─────────────────────────────────┘
                        │ HTTP / fetch (Axios)

┌─────────────────────────────────────────────────────────┐
│              Next.js 15 — port 3000                     │
│                                                         │
│  (auth) group         (dashboard) group                 │
│  /login               /dashboard                        │
│  /register            /projects  /tasks  /users         │
│                       /admin                            │
│                                                         │
│  Services layer  →  Axios calls to API                  │
│  Zustand store   →  token + user state                  │
└───────────────────────┬─────────────────────────────────┘
                        │ HTTP/JSON  →  http://localhost:3001/api/v1
                        │ Authorization: Bearer <token>

┌─────────────────────────────────────────────────────────┐
│              Express 5 — port 3001                      │
│                                                         │
│  Middleware pipeline                                    │
│    httpLogger → authenticateToken → authorizeRole       │
│                                                         │
│  Modules (domain-driven)                                │
│    auth/  usuarios/  proyectos/  tareas/                │
│                                                         │
│  Each module: routes → controller → service → repo      │
└───────────────────────┬─────────────────────────────────┘
                        │ pg (direct SQL queries)

┌─────────────────────────────────────────────────────────┐
│              PostgreSQL 16                              │
│                                                         │
│  USERS  →  PROJECTS  →  TASKS  →  COMMENTS             │
└─────────────────────────────────────────────────────────┘

Request / response flow

1

Browser initiates a request

The user performs an action in the Next.js UI (e.g. loading the projects list). A React hook calls a function in the services layer.
2

Axios sends the API call

The services layer uses an Axios instance preconfigured with baseURL: http://localhost:3001/api/v1. For protected routes the Authorization: Bearer <token> header is attached from the Zustand auth store.
3

Express receives and validates the request

The request passes through the middleware pipeline: httpLogger records the method and URL, authenticateToken verifies the JWT and attaches req.usuario, and authorizeRole confirms the user’s role is permitted for that route.
4

Controller delegates to the service

The matching controller calls the service function. The service applies business rules (e.g. a PM may only archive their own projects) and calls the repository.
5

Repository executes a SQL query

The repository runs a direct pg query against PostgreSQL and returns rows to the service.
6

Response is sent as JSON

The controller formats the result using successResponse or errorResponse helpers and sends a JSON response. Axios resolves the promise; the hook updates component state.

CORS configuration

The Express server allows requests only from the Next.js origin. This is configured in back/src/app.js:
const corsOptions = {
  origin: 'http://localhost:3000',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true
};
Both the origin value and the Axios baseURL in the frontend must match the actual hostnames and ports in production. Update them together when deploying.

Authentication flow

1

Login request

The frontend POSTs credentials to POST /api/v1/login. The auth service verifies the password with Argon2id and, if valid, calls createJWT(id_usuario, rol).
2

JWT issued

The server returns a signed JWT containing { id, rol } with a 30-minute expiry. The signing secret is read from the TOKEN environment variable.
3

Token stored in Zustand

The frontend stores the token and user object in the Zustand auth store (persisted to localStorage under the key auth-storage).
4

Subsequent requests

Every Axios request to a protected endpoint sends Authorization: Bearer <token>. The authenticateToken middleware decodes the token and attaches req.usuario before the controller runs.
5

Role check

After authentication, authorizeRole([roles]) queries the database to confirm the user’s current role and rejects requests from users without the required role with a 403 response.
The current implementation uses a single access token with no refresh token. A expired token requires the user to log in again. See the trade-offs section in the source ARCHITECTURE.md for the planned improvement.

Why two separate applications?

The backend and frontend are independent Node.js projects with separate package.json files, separate dev servers, and no shared runtime code. This separation means:
  • The API can be developed, deployed, and scaled independently of the UI.
  • The Express server can serve mobile clients or third-party integrations without changes.
  • Frontend and backend teams can work in parallel without merge conflicts in shared code.
  • The Swagger UI at /api-docs documents the API contract as a first-class artifact.

Backend architecture

Domain-driven module structure, middleware pipeline, Knex.js migrations, and logging.

Frontend architecture

Next.js App Router layout, Zustand state, services layer, and shadcn/ui components.

Build docs developers (and LLMs) love