1.0.0 • Published 2 years ago

repulser v1.0.0

Weekly downloads
-
License
UNLICENSED
Repository
github
Last release
2 years ago

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) => {
  // create user
  ctx.reply({
    user: {},
    message: 'User created successfully',
  });
});

router.put('/users/:id', async (ctx) => {
  // update user
  ctx.reply({
    user: {
      id: ctx.params.id,
    },
    message: 'User updated successfully',
  });
});


router.delete('/users/:id', async (ctx) => {
  // delete user
  ctx.reply({
    user: {
      id: ctx.params.id,
    },
    message: 'User deleted successfully',
  });
});

router.get('/users/:id/profile-picture', async (ctx) => {
  // delete user
  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;
  }

  // handle json response
  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

// USER ROUTER STARTS
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) => {
  // create user
  ctx.reply({
    user: {},
    message: 'User created successfully',
  });
});

usersRouter.put('/:id', async (ctx) => {
  // update user
  ctx.reply({
    user: {
      id: ctx.params.id,
    },
    message: 'User updated successfully',
  });
});


usersRouter.delete('/:id', async (ctx) => {
  // delete user
  ctx.reply({
    user: {
      id: ctx.params.id,
    },
    message: 'User deleted successfully',
  });
});

// USER ROUTER ENDS

// USER PROFILE ROUTER STARTS
const userProfileRouter = new Router({
  prefix: '/:id/profile',
});

userProfileRouter.get('/', async (ctx) => {
  // get user profile
  ctx.reply({
    profile: {
      id: ctx.params.id,
    },
  });
});

userProfileRouter.put('/', async (ctx) => {
  // update user profile
  ctx.reply({
    profile: {
      id: ctx.params.id,
    },
  });
});


userProfileRouter.get('/picture', async (ctx) => {
  // delete user
  ctx.reply(fs.createReadStream('path/to/image'));
});

// USER PROFILE ROUTER ENDS

// ATTACH USER PROFILE ROUTER WITH USER ROUTER
usersRouter.use(userProfileRouter);

// CATEGORY ROUTER STARTS
const categoriesRouter = new Router({
  prefix: '/categories',
});

categoriesRouter.get('/', (ctx) => {
  ctx.reply({
    categories: [],
  });
});

categoriesRouter.post('/', (ctx) => {
  ctx.reply({
    category: {},
    message: 'Category created successfully',
  });
});
// CATEGORY ROUTER ENDS

// PRODUCT ROUTER STARTS
const productsRouter = new Router({
  prefix: '/products',
  // merge instead of appending/prefixing path
  merge: true,
});

productsRouter.get('/', (ctx) => {
  ctx.reply({
    products: [],
  });
});

productsRouter.post('/', (ctx) => {
  ctx.reply({
    product: {},
    message: 'Product created successfully',
  });
});
// PRODUCT ROUTER ENDS

// merge products router with category router
categoriesRouter.use(productsRouter);

// MAIN ROUTER STARTS
const router = new Router();

router.get('/', async (ctx) => {
  ctx.reply('Welcome to my API', 200, 'text/html');
});
// CATEGORY ROUTER ENDS

// attach user router
router.use(usersRouter);
// attach category router under prefix /catalogues, do category route will be /catalogues/categories
router.use('/catalogues', categoriesRouter);

// use main router 

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

  • router level middlewares
const router = new Router({
  /*
  note: route.handler will be converted to array
        you can modify the behaviour using
  addMiddleware(route: Route, middleware: CallableFunction)
  */
});

router.use((ctx, next) => {
  // will add this middlewares to every route 
  // note: route.handler will be converted to an Array (no change, if already an Array)
})
  • route handler modifier
import compose from 'koa-compose';

const router = new Router({
  composeHandler(route: Route) {
    route.handler = compose(route.handler)
  },  
});
1.0.0

2 years ago

1.0.0-beta

2 years ago

0.1.2

3 years ago

0.1.1

3 years ago

0.1.0

3 years ago