Repulser
Repulser providers various types of routing strategies.
Note: This package supports both esm and commonjs.
Installation
npm install repulser
Usage
Simple usage
const { createServer } = require('http');
const fs = require('fs');
const stream = require('stream');
const { Router } = require('../cjs');
function isReadableStream(obj) {
return obj instanceof stream.Stream &&
typeof (obj._read === 'function') &&
typeof (obj._readableState === 'object');
}
const router = new Router();
router.get('/', async (ctx) => {
ctx.reply('Welcome to my API', 200, 'text/html');
});
router.get('/users', async (ctx) => {
ctx.reply({
users: [],
});
});
router.get('/users/:id', async (ctx) => {
ctx.reply({
user: {
id: ctx.params.id,
},
});
});
router.post('/users', async (ctx) => {
ctx.reply({
user: {},
message: 'User created successfully',
});
});
router.put('/users/:id', async (ctx) => {
ctx.reply({
user: {
id: ctx.params.id,
},
message: 'User updated successfully',
});
});
router.delete('/users/:id', async (ctx) => {
ctx.reply({
user: {
id: ctx.params.id,
},
message: 'User deleted successfully',
});
});
router.get('/users/:id/profile-picture', async (ctx) => {
ctx.reply(fs.createReadStream('path/to/image'));
});
router.any('/users/any/profile', async (ctx) => {
switch (ctx.method) {
case 'GET':
ctx.reply({ message: 'Get user profile route!' })
break
case 'POST':
ctx.reply({ message: 'Create user profile route!' })
break
case 'PUT':
ctx.reply({ message: 'Update user profile route!' })
break
case 'DELETE':
ctx.reply({ message: 'Delete user profile route!' })
break
default:
ctx.reply({ message: 'Method not supported!' })
break
}
});
router.all(['GET', 'PUT', 'DELETE'], '/users/all/address', async (ctx) => {
switch (ctx.method) {
case 'GET':
ctx.reply({ message: 'Get user address route!' })
break
case 'PUT':
ctx.reply({ message: 'Add/Update user address route!' })
break
case 'DELETE':
ctx.reply({ message: 'Delete user address route!' })
break
}
});
createServer(async (req, res) => {
const ctx = {
method: req.method,
req,
res,
reply: (body, status = 200, type = 'application/json') => {
ctx.response = { body, status, type };
},
};
[ctx.url, ctx.query] = req.url.split('?');
ctx.query = new URLSearchParams(ctx.query);
ctx.params = {};
const route = router.find(req.method, ctx.url, ctx.params);
if (!route) {
res.statusCode = 404;
res.end(JSON.stringify({
error: 'Page not found',
}));
return;
}
await route.handler(ctx);
if (!ctx.response) {
res.statusCode = 204;
res.end();
return;
}
res.statusCode = ctx.response.status || 200;
if (ctx.method == 'HEAD') {
res.statusCode = 204;
res.end();
return;
}
if (ctx.response.type === 'application/json') {
res.setHeader('content-type', 'application/json');
res.end(JSON.stringify(ctx.response.body));
return;
}
if (isReadableStream(ctx.response.body)) {
res.setHeader('content-type', ctx.response.type || 'application/octet-stream');
ctx.response.pipe(res);
return;
}
res.setHeader('content-type', ctx.response.type || 'text/plain');
res.end(ctx.response.body);
}).listen(3000, () => {
console.info('Server listening on :3000');
})
Advance usage
const usersRouter = new Router({
prefix: '/users'
});
usersRouter.get('/', async (ctx) => {
ctx.reply({
users: [],
});
});
usersRouter.get('/:id', async (ctx) => {
ctx.reply({
user: {
id: ctx.params.id,
},
});
});
usersRouter.post('/', async (ctx) => {
ctx.reply({
user: {},
message: 'User created successfully',
});
});
usersRouter.put('/:id', async (ctx) => {
ctx.reply({
user: {
id: ctx.params.id,
},
message: 'User updated successfully',
});
});
usersRouter.delete('/:id', async (ctx) => {
ctx.reply({
user: {
id: ctx.params.id,
},
message: 'User deleted successfully',
});
});
const userProfileRouter = new Router({
prefix: '/:id/profile',
});
userProfileRouter.get('/', async (ctx) => {
ctx.reply({
profile: {
id: ctx.params.id,
},
});
});
userProfileRouter.put('/', async (ctx) => {
ctx.reply({
profile: {
id: ctx.params.id,
},
});
});
userProfileRouter.get('/picture', async (ctx) => {
ctx.reply(fs.createReadStream('path/to/image'));
});
usersRouter.use(userProfileRouter);
const categoriesRouter = new Router({
prefix: '/categories',
});
categoriesRouter.get('/', (ctx) => {
ctx.reply({
categories: [],
});
});
categoriesRouter.post('/', (ctx) => {
ctx.reply({
category: {},
message: 'Category created successfully',
});
});
const productsRouter = new Router({
prefix: '/products',
merge: true,
});
productsRouter.get('/', (ctx) => {
ctx.reply({
products: [],
});
});
productsRouter.post('/', (ctx) => {
ctx.reply({
product: {},
message: 'Product created successfully',
});
});
categoriesRouter.use(productsRouter);
const router = new Router();
router.get('/', async (ctx) => {
ctx.reply('Welcome to my API', 200, 'text/html');
});
router.use(usersRouter);
router.use('/catalogues', categoriesRouter);
router.find(method, path);
With koajs
app.use(async (ctx, next) => {
ctx.params = {}
const route = router.find(ctx.method.toUpperCase(), ctx.url, ctx.params);
if (!route) {
throw new Error('Page not found.');
}
await route.handler(ctx);
await next();
});
With expressjs
app.use((req, res, next) => {
req.params = {}
const route = router.find(req.method.toUpperCase(), req.url, req.params);
if (!route) {
return next(new Error('Page not found.'));
}
await route.handler(req, res, next);
});
Extra Features
const router = new Router({
});
router.use((ctx, next) => {
})
import compose from 'koa-compose';
const router = new Router({
composeHandler(route: Route) {
route.handler = compose(route.handler)
},
});