Getting Started with TinyREST — Build Fast, Minimal APIsTinyREST is a minimalist REST framework designed for developers who want to build small, fast APIs with minimal overhead. It emphasizes simplicity, low memory footprint, and clear conventions that allow you to launch endpoints quickly without sacrificing maintainability. This guide walks through installing TinyREST, creating a simple API, handling routing and middleware, testing, and deployment patterns and best practices.
Why choose TinyREST?
- Minimal footprint: Small binary/installation size and low runtime overhead.
- Simple API: A tiny set of concepts to learn — routes, handlers, middleware.
- Fast startup: Ideal for microservices, serverless functions, and edge deployments.
- Opinionated defaults: Sensible defaults reduce boilerplate while remaining configurable.
Installation and project setup
Assuming TinyREST is distributed as an npm package (adjust commands to your runtime/package manager if different), start a new project:
-
Initialize project:
mkdir tinyrest-demo cd tinyrest-demo npm init -y
-
Install TinyREST and common tools:
npm install tinyrest npm install --save-dev nodemon jest supertest
-
Create basic project structure:
tinyrest-demo/ ├─ src/ │ ├─ index.js │ ├─ routes/ │ │ └─ users.js │ └─ middleware/ │ └─ logger.js ├─ tests/ │ └─ users.test.js ├─ package.json
Creating your first TinyREST server
Open src/index.js and create a minimal server:
const TinyREST = require('tinyrest'); const users = require('./routes/users'); const logger = require('./middleware/logger'); const app = new TinyREST(); app.use(logger); // global middleware app.register('/users', users); app.listen(3000, () => { console.log('TinyREST server listening on http://localhost:3000'); });
This example demonstrates TinyREST’s primary concepts:
- app.use(middleware) — register middleware that runs before route handlers.
- app.register(path, router) — mount a router at a path.
- app.listen(port, cb) — start the server.
Defining routes
Create src/routes/users.js:
const Router = require('tinyrest/router'); const router = new Router(); // In-memory store for demo const users = [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' } ]; router.get('/', (req, res) => { res.json(users); }); router.get('/:id', (req, res) => { const id = Number(req.params.id); const user = users.find(u => u.id === id); if (!user) return res.status(404).json({ error: 'Not found' }); res.json(user); }); router.post('/', async (req, res) => { const body = await req.json(); // TinyREST provides a small req.json() helper const id = users.length ? users[users.length - 1].id + 1 : 1; const newUser = { id, ...body }; users.push(newUser); res.status(201).json(newUser); }); module.exports = router;
Notes:
- Router exposes methods for HTTP verbs (get, post, put, delete, etc.).
- Handlers receive (req, res). req usually has helpers like json(), params, query, and headers.
- res provides json(), status(), send(), and other minimal helpers.
Middleware
Create src/middleware/logger.js:
module.exports = (req, res, next) => { const start = Date.now(); res.onFinish(() => { const ms = Date.now() - start; console.log(`${req.method} ${req.path} — ${res.statusCode} ${ms}ms`); }); next(); };
TinyREST middleware follows the familiar (req, res, next) signature. It can be synchronous or asynchronous (returning a Promise). Middleware can modify req/res, short-circuit responses, or call next() to continue.
Error handling
TinyREST includes a simple error-handling pattern. Use try/catch in async handlers or register a global error handler.
Example global error handler:
app.use((err, req, res, next) => { console.error(err); res.status(500).json({ error: 'Internal Server Error' }); });
For async handlers, ensure rejected promises are caught or the framework will forward them to the error handler.
Validation and request parsing
TinyREST keeps parsers minimal but supports common use cases:
- req.json(): parse JSON body (throws on invalid JSON).
- req.text(), req.form() for other content types.
- For validation, integrate lightweight libraries like ajv or zod.
Example using zod:
const { z } = require('zod'); const userSchema = z.object({ name: z.string().min(1) }); router.post('/', async (req, res) => { const body = await req.json(); const result = userSchema.safeParse(body); if (!result.success) return res.status(400).json({ error: result.error.errors }); // create user... });
Testing
Use Jest and Supertest for API tests. Example tests/tests/users.test.js:
const request = require('supertest'); const TinyREST = require('tinyrest'); const usersRouter = require('../src/routes/users'); let app; beforeAll(() => { app = new TinyREST(); app.register('/users', usersRouter); }); test('GET /users returns list', async () => { const res = await request(app.server).get('/users'); expect(res.statusCode).toBe(200); expect(Array.isArray(res.body)).toBe(true); }); test('POST /users creates user', async () => { const res = await request(app.server) .post('/users') .send({ name: 'Charlie' }) .set('Content-Type', 'application/json'); expect(res.statusCode).toBe(201); expect(res.body.name).toBe('Charlie'); });
TinyREST exposes an underlying server object (e.g., app.server or app.callback()) suitable for Supertest.
Performance tips
- Prefer small, focused routes—split responsibilities across services.
- Use streaming for large payloads instead of buffering full bodies.
- Cache responses or use ETags for frequently requested resources.
- Use connection pooling for databases and keep handlers non-blocking.
Deployment patterns
- Serverless functions: TinyREST’s small startup cost suits AWS Lambda, Cloudflare Workers, Vercel, etc. Wrap the app in the provider’s handler.
- Containers: Build small images (alpine base) and use multi-stage builds to reduce size.
- Edge: For edge runtimes, adapt middleware that uses provided APIs and avoid Node-only modules.
Security best practices
- Validate and sanitize all input; use typed validation (zod/ajv).
- Implement rate limiting and IP-based throttling.
- Use HTTPS and secure headers (CSP, HSTS, X-Content-Type-Options).
- Protect against common vulnerabilities (CSRF for browser apps, SQL injection via parameterized queries).
Extending TinyREST
- Logging: add structured logging with pino or bunyan.
- Monitoring: expose a /health and /metrics endpoint (Prometheus).
- Authentication: plug in JWT or OAuth middleware at the app or route level.
- GraphQL: combine TinyREST with a GraphQL handler for select routes if you need flexible queries.
Example: Adding JWT auth to /users
const jwt = require('jsonwebtoken'); const auth = (req, res, next) => { const authHeader = req.headers.authorization; if (!authHeader) return res.status(401).json({ error: 'Missing token' }); const token = authHeader.split(' ')[1]; try { req.user = jwt.verify(token, process.env.JWT_SECRET); next(); } catch (err) { res.status(401).json({ error: 'Invalid token' }); } }; app.use('/users', auth); // protects /users routes
Conclusion
TinyREST is built for developers who want straightforward, fast APIs without heavy frameworks. Its minimalism leads to clarity and performance while remaining flexible enough to integrate with common libraries for validation, auth, and observability. Start small, keep routes focused, and layer features via middleware.
If you want, I can:
- produce a TypeScript version of the examples,
- create a Dockerfile and deployment pipeline,
- or write a tutorial converting an Express app to TinyREST.
Leave a Reply