The InfoJobs DevBoard backend is an Express 5 REST API written with ES Modules. It follows a layered architecture where each concern lives in its own directory: routes declare URL patterns, controllers hold business logic, models handle data access, and Zod schemas validate every incoming payload. Two middleware layers — CORS and a per-route rate limiter — sit in front of the AI endpoint to control access and prevent abuse.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/mauroperez055/infoJobs/llms.txt
Use this file to discover all available pages before exploring further.
Directory structure
Layered architecture
Every HTTP request flows through the same pipeline before a response is returned:Configuration
All server-wide constants and environment-driven settings are exported fromconfig.js:
DEFAULTS.PORT is used by index.js as the fallback when process.env.PORT is not set. CONFIG.MODEL_AI is exported for future use but is currently not wired into the AI route — the /ai/summary/:id handler hard-codes model: 'qwen2.5:3b' directly in the ollama.chat() call.
Router modules
/jobs — job listing CRUD
Defined in routes/jobs.js, mounted at /jobs in index.js. Every write operation (POST and PATCH) passes through an inline Zod validation middleware before reaching the controller; invalid payloads receive a 400 response with structured error details before the controller is ever called.
| Method | Path | Validation | Controller action |
|---|---|---|---|
GET | /jobs | — | JobController.getAll |
GET | /jobs/:id | — | JobController.getId |
POST | /jobs | validateJob (full schema) | JobController.create |
PUT | /jobs/:id | — | JobController.update |
PATCH | /jobs/:id | validatePartialJob (partial schema) | JobController.parcialUpdate |
DELETE | /jobs/:id | — | JobController.delete |
/ai — AI job summaries
Defined in routes/ai.js, mounted at /ai in index.js. A express-rate-limit instance is applied as router-level middleware, limiting each IP to 5 requests per minute with standard RateLimit response headers (draft-8 spec). Requests that exceed the limit receive a 429 with a JSON error message.
The single endpoint GET /ai/summary/:id works as follows:
Look up the job
JobModel.getById(id) reads the matching record from jobs.json. Returns 404 if not found.Build the prompt
A multi-line Spanish-language prompt is assembled from the job’s
titulo, empresa, ubicacion, and descripcion fields, instructing the model to produce a 4–6 sentence Spanish-language summary.Stream from Ollama
ollama.chat() is called with model: 'qwen2.5:3b' and stream: true. Response headers are set to Content-Type: text/plain; charset=utf-8 and Transfer-Encoding: chunked.Forward chunks to the client
Each chunk from the async iterator is written directly to the response with
res.write(content). The response is finalised with res.end() once the stream is exhausted.CORS middleware
middlewares/cors.js exports a corsMiddleware factory that wraps the cors npm package with an explicit origin allowlist:
Origin header (e.g. server-to-server or same-origin calls) are allowed through unconditionally. Browser requests from any origin not in ACCEPTED_ORIGINS are rejected with an error. In the current index.js the factory is commented out in favour of cors() with open defaults — use corsMiddleware() to lock down the allowed origins in production.
Running the server
Servidor levantado en http://localhost:1234 on startup. The PORT environment variable overrides the default. The server also sets app.set('trust proxy', 1) so that the rate limiter reads the real client IP when the app is running behind a reverse proxy such as Nginx or Cloudflare.