crudella

Tool for developing generic service layer for RESTful CRUD API in your Node.js backend application

Usage no npm install needed!

<script type="module">
  import crudella from 'https://cdn.skypack.dev/crudella';
</script>

README

Crudella Build Status Coverage Status Dependency Status Npm Types Prettier Docs License

Tool for developing generic service layer for RESTful CRUD API in your Node.js backend application.

Crudella

Why Crudella

Crudella is a service factory. It creates your service module based on the minimal required configuration you provide. Assume you have an entity repository (or any other CRUD interface for resource) and to make it work, you cannot simply bind it to routes, skipping through controller and service layer. Here are several random problems you face in the mentioned layers:

  • User authorization
  • Request validation (this resource cannot be deleted, only some attributes can be updated etc.)
  • Data transformation (before creating or updating resource)
  • Verify that resource exists before update or delete
  • Pass custom HTTP context to the validation functions

Crudella implements logic equivalent of the controller and the service layer. It goes as far as possible to help you with menial repeated code, but hopefully not as much as to completely take over your application and magically orchestrate behind your back.

Crudella helps you in various ways:

  • Remove lot of boilerplate code: This not only saves you work, but also gives you space to focus on the non-generic code.
  • Simplifies testing: Don't waste your time on running lengthy integration tests for boring CRUD over and over again. Test business logic, not boilerplate.
  • Separation of concerns: Do not mix your service logic with validation or authorization. Crudella encourages you to decouple your service logic from client validation and orchestrates the calls for you.

Usage

Install via npm:

npm i crudella

Crudella is tested on several major Node.js versions starting at 6.

Getting started

// dalmatianService.ts
import { createService } from 'crudella';

const dalmatianService = createService<DalmatianAttributes>({
    // each method receives relevant CRUD context
    detail: ctx => dalmatianRepository.find(ctx.id, ctx.options),
    create: ctx => dalmatianRepository.create(ctx.data, ctx.options),
    update: ctx => dalmatianRepository.updateById(ctx.entity.id, ctx.data, ctx.options),
    delete: ctx => dalmatianRepository.deleteById(ctx.entity.id),
    list: ctx => dalmatianRepository.list(ctx.filters, ctx.options),
});

export const createPupperMiddleware = dalmatianService.createMiddleware;

Based on that Crudella provides an express middleware you can use without further hassle:

// routes.ts
import { createPupperMiddleware } from 'dalmatianService';

const router = createRouter();
router.use(createPupperMiddleware('/api/puppies'))

That saved us some time. We don't have to bind the routes ourselves. More importantly though, we can control the flow using rich contextual data. See authorization.

:information_source: Not using express? You can still use Crudella. See generating handlers.

:information_source: This definition still feels too long and you have consistent repository API? See configuring crudella using repository.

Advanced topics

Authorization

Crudella allows you to decouple pure data manipulation (persisting puppies) from request verification. Use authorize option with rich contextual data to reject access due to validation / authorization and do not pollute logic of manipulating objects with unrelated code.

import { Access, createService } from 'crudella';

const dalmatianService = createService<DalmatianAttributes>({
    // ...all crud methods
    authorize: async ctx => {
        // reject anonymous users for all actions (detail, create, update, delete, list)
        // ctx.context is type of C, which you can set for your application
        if (!ctx.context.user) {
            throw new Error('Anonymous cannot see, delete, update or create dalmatians')
        }
        // reject for selected operations
        if (ctx.type === Access.LIST && 'dots' in filters) {
            throw new Error('Is rude to filter using dots!')
        }
        if (ctx.type === Access.CREATE && !isValidPuppy(ctx.data)) {
            throw new Error('Your puppy is invalid, sorry.')
        }
        // ctx contains all you need for validation and authorization
        if (ctx.type === Access.UPDATE) {
            if (!age in ctx.bareData) { // only data sent by client
                throw new Error('Age missing on update')
            }
            if (!isValidPuppy(ctx.data)) { // new data defaulted to existing entity
                throw new Error('Validation failed')
            }
        }
    },
});

For full reference of the contexts, see API docs.

Other topics

Development

Building

Run npm run build to compile Typescript into JavaScript.

Testing

Project uses Jest testing framework and its snapshot testing. Run npm run test to test or npm run test:coverage to collect coverage.

Travis CI tests PRs, Coveralls collect coverage.

Coding style

TS lint and prettier npm run lint

Docs

To generate API docs using TypeDoc and preview locally, run npm run docs. Output is an ignored docs folder. Current API documentation is deployed by Travis via GitHub Pages :octocat:

License

This project is licensed under MIT.