Proof of Concept for a RESTful Web Service built with Express.js 5 and TypeScript targeting Node.js 24 LTS. This project demonstrates best practices for building a layered, testable, and maintainable API implementing CRUD operations for a Players resource (Argentina 2022 FIFA World Cup squad).
- ποΈ Layered Architecture - Interface-based design with constructor injection and strict TypeScript
- π Interactive Documentation - Live API exploration with Swagger UI and VS Code REST Client support
- β‘ Performance Caching - In-memory caching with node-cache and Sequelize ORM
- π Security Middleware - Rate limiting, CORS, and Helmet headers
- π³ Containerized Deployment - Multi-stage Docker builds with pre-seeded database
- π Automated Pipeline - Continuous integration with ESLint, Prettier, and automated testing
| Category | Technology |
|---|---|
| Runtime | Node.js 24 (LTS/Krypton) |
| Language | TypeScript 5.9 |
| Module System | Native ECMAScript Modules (ESM) - uses tsx for execution |
| Framework | Express.js 5 |
| Database | SQLite3 with Sequelize ORM |
| Caching | node-cache |
| Documentation | Swagger (OpenAPI 3.0) |
| Security | Helmet, CORS, express-rate-limit |
| Testing | Jest 30 with Supertest |
| Containerization | Docker with multi-stage builds |
| Code Quality | ESLint, Prettier, Commitlint |
| Dev Tools | tsx (TypeScript executor), nodemon |
π‘ Note: While the repository name references
ts-node(the original implementation), the project now uses tsx for faster, cleaner TypeScript execution without experimental flags.
Layered architecture with dependency injection via constructors and interface-based contracts.
%%{init: {
"theme": "default",
"themeVariables": {
"fontFamily": "Fira Code, Consolas, monospace",
"textColor": "#555",
"lineColor": "#555",
"lineWidth": 2,
"clusterBkg": "#f5f5f5",
"clusterBorder": "#999"
}
}}%%
graph RL
tests[tests]
subgraph Layer 1[" "]
server[server]
app[app]
end
subgraph Layer 2[" "]
routes[routes]
controllers[controllers]
Express[Express]
end
subgraph Layer 3[" "]
services[services]
nodeCache[node-cache]
end
subgraph Layer 4[" "]
database[database]
Sequelize[Sequelize]
end
models[models]
%% Dependencies
app --> server
routes --> app
controllers --> routes
services --> controllers
database --> services
Express --> routes
nodeCache --> services
Sequelize --> database
Express --> app
Express -.-> controllers
Sequelize -.-> models
app -.-> tests
models -.-> database
models -.-> services
models -.-> controllers
controllers --> app
services --> app
database --> app
%% Styling
classDef core fill:#b3d9ff,stroke:#6db1ff,stroke-width:2px,color:#555,font-family:monospace;
classDef deps fill:#ffcccc,stroke:#ff8f8f,stroke-width:2px,color:#555,font-family:monospace;
classDef test fill:#ccffcc,stroke:#53c45e,stroke-width:2px,color:#555,font-family:monospace;
class server,app,routes,controllers,services,database,models core
class Express,Sequelize,nodeCache deps
class tests test
Arrows follow the injection direction (A β B means A is injected into B). Solid = runtime dependency, dotted = structural. Blue = core domain, red = third-party, green = tests.
Significant architectural decisions are documented in docs/adr/.
Interactive API documentation is available via Swagger UI at http://localhost:9000/swagger/ when the server is running.
| Method | Endpoint | Description | Status |
|---|---|---|---|
GET |
/players |
List all players | 200 OK |
GET |
/players/:id |
Get player by ID | 200 OK |
GET |
/players/squadNumber/:squadNumber |
Get player by squad number | 200 OK |
POST |
/players |
Create new player | 201 Created |
PUT |
/players/squadNumber/:squadNumber |
Update player by squad number | 204 No Content |
DELETE |
/players/squadNumber/:squadNumber |
Remove player by squad number | 204 No Content |
GET |
/health |
Health check | 200 OK |
Error codes: 400 Bad Request (validation failed) Β· 404 Not Found (player not found) Β· 409 Conflict (duplicate squad number on POST)
For complete endpoint documentation with request/response schemas, explore the interactive Swagger UI. You can also access the OpenAPI JSON specification at http://localhost:9000/swagger.json.
Alternatively, use rest/players.rest with the REST Client extension for VS Code to send requests directly from the editor.
Before you begin, ensure you have the following installed:
- Node.js (see
.nvmrcfor required version) - npm (comes with Node.js)
- direnv (optional, but recommended β auto-loads the correct Node.js version via
.nvmrcon directory entry) - Docker and Docker Compose (optional, for containerized setup)
git clone https://github.com/nanotaboada/ts-node-samples-express-restful.git
cd ts-node-samples-express-restfulnpm installnpm run devThe server will start on http://localhost:9000 with the following output:
> ts-node-samples-express-restful@1.0.0 dev
> nodemon
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): src/**/*
[nodemon] watching extensions: ts
[nodemon] starting `tsx ./src/server.ts`
π Running at http://localhost:9000Once the application is running, you can access:
- API Server:
http://localhost:9000 - Swagger UI:
http://localhost:9000/swagger/ - Health Check:
http://localhost:9000/health
npm run docker:up
# or
docker compose upπ‘ Note: On first run, the container copies a pre-seeded SQLite database into a persistent volume. On subsequent runs, that volume is reused and the data is preserved.
npm run docker:down
# or
docker compose downTo remove the volume and reinitialize the database from the built-in seed file:
docker compose down -vEach release publishes multiple tags for flexibility:
# By semantic version (recommended for production)
docker pull ghcr.io/nanotaboada/ts-node-samples-express-restful:1.0.0
# By term name (memorable alternative)
docker pull ghcr.io/nanotaboada/ts-node-samples-express-restful:assist
# Latest release
docker pull ghcr.io/nanotaboada/ts-node-samples-express-restful:latestCreate a .env file in the root directory to customize configuration:
# Server port (default: 9000)
PORT=9000
# Database storage path (default: storage/players-sqlite3.db)
STORAGE_PATH=storage/players-sqlite3.db
# Rate limiting (all optional β defaults shown)
RATE_LIMIT_ENABLED=true
RATE_LIMIT_WINDOW_MS=60000
RATE_LIMIT_MAX_GENERAL=100
RATE_LIMIT_MAX_STRICT=20Contributions are welcome! Please see CONTRIBUTING.md for details on:
- Code of Conduct
- Development workflow and best practices
- Commit message conventions (Conventional Commits)
- Pull request process and requirements
Key guidelines:
- Follow Conventional Commits for commit messages
- Ensure all tests pass (
npm test) - Run linter before committing (
npm run lint) - Keep changes small and focused
- Review
.github/copilot-instructions.mdfor architectural patterns
Testing:
Run the test suite with Jest + Supertest:
# Run all tests
npm test
# Run tests with coverage report
npm run coverage| Command | Description |
|---|---|
npm run dev |
Start development server with hot reload |
npm start |
Run compiled application from dist/ |
npm run build |
Compile TypeScript to JavaScript |
npm test |
Run Jest tests |
npm run coverage |
Generate test coverage report |
npm run lint |
Run ESLint on all files |
npm run lint:commit |
Validate last commit message format |
npm run swagger:docs |
Generate swagger.json from JSDoc annotations |
npm run docker:build |
Build Docker image |
npm run docker:up |
Start Docker container |
npm run docker:down |
Stop and remove Docker volume |
| AI Commands | |
/pre-commit |
Runs linting, tests, and quality checks before committing |
/pre-release |
Runs pre-release validation workflow |
This project is provided for educational and demonstration purposes and may be used in production at your own discretion. All trademarks, service marks, product names, company names, and logos referenced herein are the property of their respective owners and are used solely for identification or illustrative purposes.