https://github.com/nextlevelshit/node-cache-api
Most simple implementation of a API Server with expressJS
https://github.com/nextlevelshit/node-cache-api
api expressjs node nodejs
Last synced: about 2 months ago
JSON representation
Most simple implementation of a API Server with expressJS
- Host: GitHub
- URL: https://github.com/nextlevelshit/node-cache-api
- Owner: nextlevelshit
- License: wtfpl
- Created: 2025-06-20T09:29:10.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2025-06-29T18:46:29.000Z (12 months ago)
- Last Synced: 2025-06-29T19:24:38.874Z (12 months ago)
- Topics: api, expressjs, node, nodejs
- Language: JavaScript
- Homepage: https://nextlevelshit.github.io/2025-node-api/
- Size: 232 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Node.js ExpressJS API (Caching Service)
A modern, minimal Node.js REST API built with ES modules, Express 5, and comprehensive testing via Vitest. Perfect for teaching contemporary web development patterns and testing strategies.
## TL;DR
```bash
npm install
npm start
# API running on http://localhost:1312
```
## What's Inside
This isn't your typical Express boilerplate. We're running:
- **Express 5.x** with ES modules (no more `require()` nonsense)
- **In-memory cache service** for blazing-fast data operations
- **Four-tier testing strategy** with static analysis, unit, integration, and E2E coverage
- **Zero dependencies** for core functionality (Express + testing tools only)
- **Modern JavaScript** patterns throughout
- **Proper error handling** and HTTP status codes
## Architecture
```
src/
├── app.js # Express app factory
├── routes/
│ └── api.js # RESTful API endpoints
└── services/
└── Cache.js # In-memory key-value store
```
The cache service handles all CRUD operations with optional debug logging and override capabilities. API routes provide a clean REST interface over HTTP.
## API Reference
### Core Endpoints
```http
GET /api # List all keys
GET /api/:key # Retrieve data by key
POST /api # Create new entry (auto-generated key)
PUT /api/:key # Update existing or create new
DELETE /api/:key # Remove entry
DELETE /api # Clear all entries
```
### Examples
**Create data:**
```bash
curl -X POST http://localhost:1312/api \
-H "Content-Type: application/json" \
-d '{"name":"Wolfgang","city":"Stuttgart"}'
# Returns: {"key":"1735401234567"}
```
**Retrieve data:**
```bash
curl http://localhost:1312/api/1735401234567
# Returns: {"name":"Wolfgang","city":"Stuttgart"}
```
**Update data (merge behavior):**
```bash
curl -X PUT http://localhost:1312/api/1735401234567 \
-H "Content-Type: application/json" \
-d '{"age":25}'
# Returns: {"key":"1735401234567","data":{"name":"Wolfgang","city":"Stuttgart","age":25}}
```
## Testing Strategy: The Four Pillars 🏛️
This project implements a comprehensive four-tier testing approach. Each tier serves a specific purpose and tests different aspects of your application with increasing realism but decreasing speed.
### 1. Static Analysis: Code Quality Checks 🔍
**What it tests:** Syntax errors and code quality issues without execution
**Tool:** ESLint
**Speed:** ⚡ ~10ms
**When to run:** Before every commit
```bash
npm run lint
```
**Key characteristics:**
- Catches syntax errors, unused variables, undefined references
- Zero runtime overhead
- First line of defense against bugs
### 2. Unit Tests: Testing in Isolation 🔬
**What they test:** Individual functions and classes with all dependencies mocked
**Tool:** Vitest with mocks
**Speed:** ⚡ ~50ms
**When to run:** Constantly during development
```javascript
// From Cache.unit.test.js
test("creates and retrieves data with custom key", () => {
const cache = new Cache();
const testData = { user: "wolfgang", city: "stuttgart" };
const key = cache.create(testData, "custom-key");
expect(key).toBe("custom-key");
expect(cache.get("custom-key")).toEqual(testData);
});
```
**Key characteristics:**
- **Complete isolation** - every dependency is mocked
- **Deterministic** - same input always produces same output
- **Fast** - no I/O, no network, no filesystem
- **Focused** - test one function at a time
### 3. Integration Tests: Component Interaction Testing 🔗
**What they test:** How your components work together, but still in-process
**Tool:** Vitest + Supertest (no real server)
**Speed:** 🟡 ~200ms
**When to run:** Before commits
```javascript
// From app.integration.test.js
import request from "supertest"; // ← Key difference: supertest, not fetch
import { createApp } from "./app.js";
test("full CRUD lifecycle with real cache", async () => {
// supertest creates a test server internally - no real HTTP
const createResponse = await request(app) // ← In-process request
.post("/api")
.send({ name: "integration-test", status: "active" });
expect(createResponse.status).toBe(201);
const { key } = createResponse.body;
// Verify it's actually in the cache (real cache instance)
expect(cache.keys).toContain(key);
});
```
**Key characteristics:**
- **Real components** - actual Cache and Express app instances
- **Supertest magic** - simulates HTTP without actual server startup
- **In-process** - everything runs in the same Node.js process
- **No network** - no real TCP connections or ports
**Further links:**
- [Unit Testing vs Integration Testing - Key Differences](https://www.alexhyett.com/unit-testing-vs-integration-testing/#:~:text=Do%20integration%20tests%20use%20mocks,running%20just%20before%20a%20release.)
### 4. E2E Tests: Full Stack Reality 🌐
**What they test:** Your entire application exactly as users experience it
**Tool:** Vitest + real fetch() + real server
**Speed:** 🔴 ~1000ms
**When to run:** Before deployments
```javascript
// From index.e2e.test.js
beforeAll(async () => {
// Start actual server on real port
server = createServer(app);
await new Promise((resolve) => server.listen(TEST_PORT, resolve));
});
test("complete user journey", async () => {
// Real HTTP request over TCP to actual running server
const createResponse = await fetch(`${baseUrl}/api`, {
// ← Real fetch, not supertest
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
message: "e2e test data",
timestamp: new Date().toISOString(),
}),
});
expect(createResponse.status).toBe(201);
});
```
**Key characteristics:**
- **Real server** - actual HTTP server listening on TCP port
- **Real network** - genuine HTTP requests over localhost
- **Real environment** - tests server startup, CORS, middleware stack
- **User perspective** - exactly what your users experience
## The Critical Differences Explained
| Test Type | Import Pattern | Request Method | Server Status | Speed | What's Real |
| --------------- | ---------------------------------- | --------------------------- | ------------- | --------- | ----------------------- |
| **Unit** | `import { Cache }` | Direct method calls | No server | ⚡ 10ms | Nothing - all mocked |
| **Integration** | `import request from "supertest"` | `request(app).get()` | Simulated | 🟡 100ms | Components, not network |
| **E2E** | `beforeAll(() => server.listen())` | `fetch("http://localhost")` | Real server | 🔴 1000ms | Everything |
### Why This Matters
**Integration tests catch:** "My cache works, my routes work, but they don't work _together_"
```javascript
// Integration - tests that cache + routes integrate properly
const response = await request(app).post("/api").send(data);
expect(cache.keys).toContain(response.body.key); // Direct cache access
```
**E2E tests catch:** "Everything works in isolation, but fails when deployed"
```javascript
// E2E - tests the complete network stack
const response = await fetch(`http://localhost:${port}/api`, { ... });
// No direct cache access - testing through the network boundary
```
## Test Execution & Development Workflow
```bash
# Development with hot reload
npm start
# Run specific test tiers (in order of feedback speed)
npm run test.unit # 50ms - Run constantly
npm run test.integration # 200ms - Run before commits
npm run test.e2e # 1000ms - Run before deployments
# Run all tests
npm test
# Coverage report
npm run test.coverage
```
### The Testing Pyramid in Action
```
E2E (Few, Slow, High Confidence)
/\
/ \
/ \ Integration (Some, Medium, Component Confidence)
/ \
/ \
/__________\ Unit (Many, Fast, Logic Confidence)
```
**Development workflow:**
1. **Write unit tests first** - fast feedback on logic
2. **Add integration tests** - verify components play nice
3. **Minimal E2E tests** - critical user journeys only
## Test Environment Setup
### Unit Tests
```javascript
// All dependencies mocked
const mockCache = {
get: vi.fn(),
create: vi.fn(),
// ... complete mock interface
};
```
### Integration Tests
```javascript
// Real instances, no mocks
cache = new Cache({ debug: false });
app = createApp(cache, { port: 1312 });
// supertest handles the HTTP simulation
```
### E2E Tests
```javascript
// Real server lifecycle
beforeAll(async () => {
server = createServer(app);
await new Promise((resolve) => server.listen(TEST_PORT, resolve));
});
afterAll(async () => {
await new Promise((resolve) => server.close(resolve));
});
```
## Cache Service Deep Dive
The `Cache` class is the heart of data persistence:
```javascript
import { Cache } from "./src/services/Cache.js";
const cache = new Cache({
debug: true, // Enable console logging
override: true, // Allow key overwrites with merge
});
// Basic operations
const key = cache.create({ user: "data" });
const data = cache.get(key);
cache.update(key, { more: "data" });
cache.remove(key);
// Collections
cache.keys; // All keys as array
cache.values; // All values as array
cache.clear(); // Nuclear option
```
## Production Considerations
This is a development/learning project. For production:
- Add persistent storage (Redis, PostgreSQL, etc.)
- Implement authentication/authorization
- Add rate limiting and request validation
- Set up proper logging (Winston, Pino)
- Add health checks and metrics
- Configure CORS appropriately
- Add database integration tests
- Set up CI/CD with all test tiers
## Why This Stack?
- **ES Modules**: The future of JavaScript modularity
- **Express 5**: Latest stable with improved async support
- **Vitest**: Faster than Jest, built for ES modules, superior DX
- **Minimal dependencies**: Less attack surface, easier auditing
- **Test-driven**: Comprehensive coverage from day one
- **Educational**: Clear separation of concerns for learning
## Testing Philosophy
> "The right test at the right level catches bugs at the right time."
This project demonstrates that **test architecture > test coverage**. Each tier serves a specific purpose:
- **Static analysis** → Catch syntax errors before runtime
- **Unit tests** → Fast feedback on logic errors
- **Integration tests** → Component interaction verification
- **E2E tests** → User experience validation
The four-tier approach ensures you catch bugs at the optimal level - fixing a unit test takes seconds, debugging an E2E failure takes minutes.