Skip to content

Latest commit

 

History

History
254 lines (167 loc) · 8.5 KB

File metadata and controls

254 lines (167 loc) · 8.5 KB

Contributing to the Node.js PostgreSQL REST API

Thank you for taking the time to contribute! This guide will help you set up the project locally, understand our architecture, and smoke test your changes before submitting code.


🛠️ Local Development Setup

We use pnpm for package management and Docker to manage infrastructure seamlessly. You do not need to install PostgreSQL locally on your machine.

Prerequisites

  • Node.js (v20 or higher)
  • pnpm
  • Docker & Docker Compose

Step 1: Clone and Install Dependencies

git clone https://github.com
cd node-postgres-api
pnpm install

Step 2: Spin Up the Environment

Start the API server alongside the isolated PostgreSQL container using Docker Compose. The orchestration configuration uses strict health checks to ensure the database engine is ready before launching the server:

docker compose up --build

The API will now be listening on http://localhost:5000.

🏗️ Architecture & Strict Coding Standards

To maintain code health and reliability, all contributions must strictly adhere to the following architecture rules:

1. Zero Usage of the any Type

We enforce type safety across the entire codebase. Using the any keyword is strictly prohibited.

  • Use unknown for values whose types are not yet determined.
  • For incoming token context structures, cast to the explicit OAuthUserContext interface.
  • To access user parameters inside Express routes, leverage our TypeScript Module Augmentation layer (req.user.sub).

2. Modern ES6 Modules

Always use native ES6 module imports/exports (import / export) instead of CommonJS (require).

  • Crucial Rule: Because Node.js requires explicit file extensions for ES modules, you must append .js to your relative file path imports (e.g., import * as db from './db.js';).

3. Request Validation with Zod

Never perform manual payload type checks inside your controllers. All endpoints must route data through our centralized validateRequest middleware using Zod schemas located in src/schemas/.

4. Controller & Middleware Layer Layout

  • Standard Data Routes: Must be wrapped with the catchAsync utility and shielded by the requireAuthentication guard middleware.
  • Dedicated OAuth Server Endpoints: (e.g., /oauth/token) must be wrapped using the custom catchOAuthAsync wrapper helper to cleanly map the custom library class models into Express response streams.

🧪 Running Automated Tests

We use Vitest for our unit and integration tests. Make sure all existing test files pass cleanly, and write new tests for any added features.

Run the test suite once

pnpm test

Run tests in continuous watch mode during development

pnpm exec vitest

🚬 How to Smoke Test Your Changes

Our server automatically pretty-prints JSON responses with a 2-space indentation layout. Before submitting a Pull Request, verify your changes manually by executing these standard local API smoke tests using side-by-side terminal windows.

🔑 Start an Authenticated Session

Because protected routes now use an HttpOnly cookie session, first request /oauth/token and save cookies to a jar file:

curl -X POST http://localhost:5000/oauth/token \
 -H "Content-Type: application/x-www-form-urlencoded" \
 -d "grant_type=client_credentials" \
 -d "client_id=demo-client-id" \
 -d "client_secret=demo-client-secret" \
 -c cookies.txt

You can verify the cookie-backed session with:

curl -X GET http://localhost:5000/oauth/session \
 -b cookies.txt \
 -w "\n"

📡 Smoke Testing WebSockets Real-Time Stream

To prevent sensitive token credentials from leaking into plaintext server tracking logs, our architecture mandates passing credentials securely via the native WebSocket Subprotocols header framework (Sec-WebSocket-Protocol).

Option A: Using the Automated Client Script

For script-based socket testing, provide a valid JWT through subprotocols (for example, from a dedicated machine-client flow):

AUTH_TOKEN=YOUR_JWT_ACCESS_TOKEN_HERE pnpm listen

Option B: Using Global Terminal Testing Tools

If testing interactively from the command line using wscat or websocat, append the required subprotocol parameters array block. Using wscat:

wscat -c ws://localhost:5000/ws -s "access_token, YOUR_JWT_ACCESS_TOKEN_HERE"

Using websocat:

websocat ws://localhost:5000/ws --header "Sec-WebSocket-Protocol: access_token, YOUR_JWT_ACCESS_TOKEN_HERE"

Testing the WebSocket Stream Message Routing: In your active socket terminal pane, pass an authenticated event payload string:

{ "event": "get_user", "payload": { "id": 6 } }

Expected Result: The socket terminal will instantly output a valid 200 OK status array containing user info object fields.

🌐 Execute REST Queries (Second Terminal Window)

Open a separate terminal pane to trigger your database changes using curl. Because the application is fully protected, include the saved cookie jar on every protected route.

1. System Diagnostic Inspection (Introspection check)

Verify your session is active and inspecting credentials correctly:

curl -X GET http://localhost:5000/secure-rest \
 -b cookies.txt \
 -w "\n"

Expected Response: 200 OK returning your decoded token metadata, user identification fields, and expiration target timestamps.

2. Create a User (POST Payload Validation)

Test Case (Fail - Missing Header / Unauthenticated):

curl -X POST http://localhost:5000/api/users \
 -H "Content-Type: application/json" \
 -d '{"name": "Realtime Dev", "email": "stream@example.com"}' \
 -w "\n"

Expected Response: 401 Unauthorized with a structured authentication missing JSON warning message block. Test Case (Pass):

curl -X POST http://localhost:5000/api/users \
 -H "Content-Type: application/json" \
 -b cookies.txt \
 -d '{"name": "Alice Developer", "email": "alice.dev@example.com"}' \
 -w "\n"

Expected Response: 201 Created returning the database entry array. Your active WebSocket listener window will instantly capture the streaming user_created block event notification.

3. Get User By ID (Route Parameter Validation)

curl -X GET http://localhost:5000/api/users/1 \
 -b cookies.txt \
 -w "\n"

Expected Response: 200 OK with the matching user row array details.

4. Update a User (PUT Data Lifecycle check)

curl -X PUT http://localhost:5000/api/users/1 \
 -H "Content-Type: application/json" \
 -b cookies.txt \
 -d '{"name": "Alice Smith", "email": "alice.smith@example.com"}' \
 -w "\n"

Expected Response: 200 OK showing updated information. Your active WebSocket terminal will intercept the matching live streaming update telemetry.

5. Delete a User (DELETE Constraint verification)

curl -X DELETE http://localhost:5000/api/users/1 \
 -b cookies.txt \
 -w "\n"

Expected Response: 200 OK confirming row deletion.

🔒 How to Smoke Test Logouts (Client-Side Session Termination)

This service uses a stateless JWT inside an HttpOnly cookie. Logging out clears the cookie on the backend.

1. Terminate Open WebSocket Tunnels

To log out of an active real-time data stream, simply shut down your terminal instance:

  • In your testing scripts (pnpm listen): Press Ctrl + C to kill the node execution loop. Your server logs will instantly track the socket shutdown and wipe the client reference from system memory (clients.delete(ws)).
  • In wscat / websocat: Press Ctrl + C or type exit to drop the connection tunnel.

2. Terminate REST Session Access

Call logout and persist the updated cookie jar:

curl -X POST http://localhost:5000/oauth/logout \
 -b cookies.txt \
 -c cookies.txt \
 -w "\n"

Verify logout blocked traffic by reusing the cookie jar:

curl -X GET http://localhost:5000/api/users -b cookies.txt

Expected Response: The request will immediately fail with a 401 Unauthorized JSON block, proving that your client session has been completely terminated.

🚀 Pull Request Checklist

Before opening your PR, make sure you can check off every step:

  1. Code compiles completely with zero TypeScript (tsc) warnings or type compilation errors.
  2. No instances of the any keyword were introduced.
  3. All relative imports explicitly feature their .js file extensions.
  4. All protected data controllers remain wrapped in catchAsync, and authentication is layered via the requireAuthentication middleware guard.
  5. pnpm test runs and all automated specs pass cleanly.
  6. Local smoke tests pass against the live running Docker backend.