A RESTful API server built with Node.js, Express, TypeScript, and PostgreSQL. Features OAuth 2.0 authentication (client credentials + authorization code flows), JWT-based access control, real-time WebSocket support, and structured request validation.
node-postgres-spa is the front end project for this service.
| Layer | Technology |
|---|---|
| Runtime | Node.js (ESM) |
| Framework | Express 5 |
| Language | TypeScript |
| Database | PostgreSQL 16 |
| Auth | OAuth 2.0 (@node-oauth/oauth2-server) + JWT |
| Validation | Zod |
| WebSockets | ws |
| Logging | Winston + Morgan |
| Testing | Vitest + Supertest |
| Container | Docker + Docker Compose |
src/
├── app.ts # Express app setup, routes, middleware wiring
├── server.ts # HTTP server + WebSocket initialization
├── db.ts # PostgreSQL connection pool
├── oauth2Model.ts # OAuth 2.0 model (token generation, validation)
├── socket.ts # WebSocket server and event handlers
├── controllers/
│ ├── authController.ts # Token exchange + authentication verification
│ ├── logoutController.ts # Logout handler
│ └── userController.ts # CRUD operations for users
├── middleware/
│ ├── authGuard.ts # JWT bearer token enforcement
│ ├── catchAsync.ts # Async error wrapper
│ ├── errorHandler.ts # Global error handler
│ ├── logger.ts # Winston logger instance
│ └── validate.ts # Zod schema request validator
├── schemas/
│ └── userSchema.ts # Zod schemas + inferred TypeScript types
└── types/
├── db.ts # Database row types
└── express.d.ts # Express type augmentations
init-db/
└── init.sql # Schema + seed data for PostgreSQL
Starts both the API server and a PostgreSQL instance. The database schema is initialized automatically via init-db/init.sql.
docker compose up --buildThe API will be available at http://localhost:5000.
-
Install dependencies
pnpm install
-
Configure environment
Create a
.envfile in the project root:PORT=5000 JWT_SECRET=demo-client-secret DB_USER=postgres DB_HOST=localhost DB_NAME=api_db DB_PASSWORD=secretpassword DB_PORT=5432
-
Start a local PostgreSQL instance (or use the Docker Compose db service only):
docker compose up db
-
Run the dev server
pnpm dev
| Command | Description |
|---|---|
pnpm dev |
Start dev server with hot reload (ts-node-dev) |
pnpm build |
Compile TypeScript to dist/ |
pnpm start |
Run compiled output (dist/server.js) |
pnpm test |
Run tests with Vitest |
All protected endpoints use an HttpOnly accessToken cookie issued by /oauth/token.
Exchange client credentials for an HttpOnly session cookie.
curl -X POST http://localhost:5000/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials&client_id=demo-client-id&client_secret=demo-client-secret"Response
{
"tokenType": "Bearer",
"accessTokenExpiresAt": "2026-01-01T00:00:00.000Z",
"scope": []
}The JWT is set as an HttpOnly cookie in the
Set-Cookieresponse header.
Returns the current authenticated session context from the cookie.
Invalidates the current session.
Verifies token validity and returns the authenticated context.
Returns all users ordered by ID.
Returns a single user by numeric ID.
Creates a new user. Also broadcasts a user_created WebSocket event to connected clients.
Request body
{
"name": "Jane Doe",
"email": "jane@example.com"
}Replaces an existing user record.
Request body — same shape as POST /api/users.
Connect to ws://localhost:5000/ws. Browser clients can authenticate via the HttpOnly cookie. The server also accepts token subprotocol authentication for non-browser clients. The server supports the following events:
| Event | Description |
|---|---|
subscribe |
Subscribe to server-pushed events |
ping |
Heartbeat keepalive |
create_user |
Create a user via WebSocket |
get_user |
Fetch a single user |
list_users |
Fetch all users |
update_user |
Replace a user record |
patch_user |
Partially update a user |
delete_user |
Delete a user |
Send a JSON message with event and payload fields:
{
"event": "list_users",
"payload": {}
}CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE oauth_clients (
id VARCHAR(255) PRIMARY KEY,
client_secret VARCHAR(255) NOT NULL,
grants TEXT[] NOT NULL,
redirect_uris TEXT[] NOT NULL
);A seed OAuth client (demo-client-id / demo-client-secret) is inserted automatically on first run.
pnpm testTests use Vitest and Supertest. Integration tests exercise the HTTP layer directly against a running database.
| Variable | Default | Description |
|---|---|---|
PORT |
5000 |
Port the HTTP server listens on |
JWT_SECRET |
demo-client-secret |
Secret used to sign and verify JWTs |
DB_USER |
— | PostgreSQL username |
DB_HOST |
— | PostgreSQL hostname |
DB_NAME |
— | PostgreSQL database name |
DB_PASSWORD |
— | PostgreSQL password |
DB_PORT |
5432 |
PostgreSQL port |
Note: Do not use the default
JWT_SECRETin production. Set a strong, randomly generated secret.