archfirst / joinjs

A JavaScript library to map complex database joins to nested objects.
MIT License
154 stars 19 forks source link

JoinJS

Build Status Coverage Status

JoinJS is a JavaScript library to map complex database joins to nested objects. It's a simpler alternative to a full-blown Object-Relation Mapper (ORM), and gives you direct control over your database interactions.

Motivation: Direct, no-nonsense control over your database

Traditional ORMs introduce a thick layer of abstraction between objects and database tables. This usually hinders, rather than helps, developer productivity. In complex use cases, it is difficult enough to devise efficient queries, but with ORMs you also have to teach them to generate the same query. It takes extra time to do this and you may not be able to produce the same query. In the worst case scenario, the ORM may hit the database multiple times for something that you were able to do in a single query.

JoinJS takes a much simpler and straightforward approach inspired by a popular Java mapping framework called MyBatis (see the post on MyBatis vs. other ORMs. You can use any database driver or query builder (such as Knex.js) to query your database, however you use JoinJS to convert the returned results into a hierarchy of nested objects.

Example

Suppose you have a one-to-many relationship between a Team and its Players. You want to retrieve all teams along with their players. Here's the query for to do this:

SELECT t.id              AS team_id,
       t.name            AS team_name,
       p.id              AS player_id,
       p.name            AS player_name
FROM   teams t
       LEFT OUTER JOIN players p
                    ON t.id = p.team_id;

Assume that this query returns the following result set:

let resultSet = [
    { team_id: 1, team_name: 'New England Patriots', player_id: 1, player_name: 'Tom Brady'      },
    { team_id: 1, team_name: 'New England Patriots', player_id: 2, player_name: 'Rob Gronkowski' },
    { team_id: 2, team_name: 'New York Jets',        player_id: 3, player_name: 'Geno Smith'     },
    { team_id: 2, team_name: 'New York Jets',        player_id: 4, player_name: 'Darrelle Revis' }
];

You can use JoinJS to convert this result set in to an array of teams with nested players:

[
    {
        id: 1,
        name: 'New England Patriots',
        players: [
            { id: 1, name: 'Tom Brady'      },
            { id: 2, name: 'Rob Gronkowski' }
        ]
    },
    {
        id: 2,
        name: 'New York Jets',
        players: [
            { id: 3, name: 'Geno Smith'     },
            { id: 4, name: 'Darrelle Revis' }
        ]
    }
]

To teach JoinJS how to do this, you must create two result maps that describe your objects:

const resultMaps = [
    {
        mapId: 'teamMap',
        idProperty: 'id',
        properties: ['name'],
        collections: [
            {name: 'players', mapId: 'playerMap', columnPrefix: 'player_'}
        ]
    },
    {
        mapId: 'playerMap',
        idProperty: 'id',
        properties: ['name']
    }
]

Once you have created these result maps, you can simply call JoinJS to convert your result set in to objects:

let mappedResult = joinjs.map(resultSet, resultMaps, 'teamMap', 'team_');

That's it! It doesn't matter how deep or complex your object hierarchy is, JoinJS can map it for you. Read the documentation below for more details. You can find more examples in the test suite. Follow the step-by-step tutorial for a hands-on introduction. Once you have mastered the basics, check out the Manage My Money project to see how you can build a full-fledged application complete with a front-end using JoinJS and other useful libraries.

Installation

$ npm install --save join-js

Don't forget the dash in the package name (join-js).

Using with ES5:

var joinjs = require('join-js').default;

Using with ES6:

import joinjs from 'join-js';

Documentation

ResultMap

ResultMaps are used to teach JoinJS how to map database results to objects. Each result map focuses on a single object. The properties of a ResultMap are described below. You can find several examples in the test suite.

API

JoinJS exposes two very simple functions that give you the full power to map any result set to one of more JavaScript objects.

map(resultSet, maps, mapId, columnPrefix)

Maps a resultSet to an array of objects.

Returns an array of mapped objects.

mapOne(resultSet, maps, mapId, columnPrefix, isRequired)

This is a convenience method that maps a resultSet to a single object. It is used when your select query is expected to return only one result (e.g. SELECT * FROM table WHERE id = 1234).

Returns the mapped object or null if no object was mapped.

Throws a NotFoundError if no object is mapped and isRequired is true.

Resources