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.
We use pnpm for package management and Docker to manage infrastructure seamlessly. You do not need to install PostgreSQL locally on your machine.
- Node.js (v20 or higher)
- pnpm
- Docker & Docker Compose
git clone https://github.com
cd node-postgres-api
pnpm installStart 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 --buildThe API will now be listening on http://localhost:5000.
To maintain code health and reliability, all contributions must strictly adhere to the following architecture rules:
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).
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';).
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/.
- 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.
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.
pnpm testpnpm exec vitestOur 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.
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.txtYou can verify the cookie-backed session with:
curl -X GET http://localhost:5000/oauth/session \
-b cookies.txt \
-w "\n"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).
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 listenIf 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.
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.
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.
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.
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.
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.
curl -X DELETE http://localhost:5000/api/users/1 \
-b cookies.txt \
-w "\n"Expected Response: 200 OK confirming row deletion.
This service uses a stateless JWT inside an HttpOnly cookie. Logging out clears the cookie on the backend.
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.
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.txtExpected Response: The request will immediately fail with a 401 Unauthorized JSON block, proving that your client session has been completely terminated.
Before opening your PR, make sure you can check off every step:
- Code compiles completely with zero TypeScript (
tsc) warnings or type compilation errors. - No instances of the
anykeyword were introduced. - All relative imports explicitly feature their
.jsfile extensions. - All protected data controllers remain wrapped in
catchAsync, and authentication is layered via therequireAuthenticationmiddleware guard. -
pnpm testruns and all automated specs pass cleanly. - Local smoke tests pass against the live running Docker backend.