JSData on the Server

JSData works with Node.js, opening up the possibly of isomorphic models. The adapters currently usable server-side are the, Firebase, RethinkDB, MongoDB, levelup, sql and Redis adapters (make one!).

Now, the server is quite a different environment than the browser with different performance and data handling considerations. Therefore, when JSData detects that it is running in Node.js it automatically changes a number of data store defaults to be more server-friendly. All dirty-checking is disabled by default when running in Node.js.

When running in Node.js:

  • cacheResponse defaults to false
  • notify defaults to false
  • reapAction defaults to "none"
  • reapInterval defaults to false
  • upsert defaults to false

If you're running multiple workers for your app then you probably don't want to be injecting data into the store, otherwise your workers would get out of sync and you might get out-of-date reads and writes depending on which worker you hit. On the other hand, if your data is small enough and you're only running one instance of your app, then allowing data store caching is a very quick way to get some ultra fast caching, as it will eliminate the need for database calls on reads (just make sure your data can all fit in memory). A caching solution that uses Redis is discussed below.

Here's an example of setting JSData up on the server:

// An example that uses the RethinkDB adapter
var DSRethinkDbAdapter = require('js-data-rethinkdb');
var rethinkdbAdapter = new DSRethinkDbAdapter({
  host: config.API_DB_HOST,
  port: config.API_DB_PORT,
  db: config.API_DB_DATABASE,
  authKey: config.API_DB_AUTH_KEY,
  min: 10,
  max: 50
});

var JSData = require('js-data');

// Here we turn off a bunch of features we don't necessarily 
// need on the server to maximize performance and avoid other
// issues
var DS = new JSData.DS({
  cacheResponse: false,
  bypassCache: true,
  keepChangeHistory: false,
  resetHistoryOnInject: false,
  upsert: false,
  notify: false,
  log: false
});
DS.registerAdapter('rethinkdb', rethinkdbAdapter, { default: true });

No caching

var DSRethinkDbAdapter = require('js-data-rethinkdb');
var rethinkdbAdapter = new DSRethinkDbAdapter();

var JSData = require('js-data');
var DS = new JSData.DS({
  // ...
  cacheResponse: false,
  bypassCache: true
  // ...
});
DS.registerAdapter('rethinkdb', rethinkdbAdapter, { default: true });

var user = DS.defineResource({
  name: 'user',
  table: 'user'
});

app.get('/users', function (req, res, next) {
  // Querying collections is literally this easy when you're
  // using JSData client and server-side because they understand
  // each other. The server-side adapters understand how to query
  // the data from the database according to JSData's query syntax,
  // the same syntax you use when calling 
  // DS#filter(resourceName[, params])
  return User.findAll(req.query).then(function (users) {
    return res.status(200).send(users).end();
  }).catch(next);
});

In-memory caching

Works when all your data fits in memory and you're only running one instance of your app.

var DSRethinkDbAdapter = require('js-data-rethinkdb');
var rethinkdbAdapter = new DSRethinkDbAdapter();

var JSData = require('js-data');

// With this approach, once data has been injected into the data
// store the app won't have to make read calls to the database
// because any updates/destroys on data will automatically
// keep what's in memory up-to-date.
var DS = new JSData.DS({
  // ...
  cacheResponse: true,
  bypassCache: false
  // ...
});
DS.registerAdapter('rethinkdb', rethinkdbAdapter, { default: true });

var user = DS.defineResource({
  name: 'user',
  table: 'user'
});

app.get('/users', function (req, res, next) {
  // Will take full advantage of JSData's in-memory caching
  return User.findAll(req.query).then(function (users) {
    return res.status(200).send(users).end();
  }).catch(next);
});

app.get('/users/:id', function (req, res, next) {
  // Will take full advantage of JSData's in-memory caching
  return User.find(req.params.id).then(function (users) {
    return res.status(200).send(users).end();
  }).catch(next);
});

Caching using Redis

This is suitable when you want caching but your data doesn't fit in memory or you're running multiple instances of your app.

// simple example
var rethinkdbAdapter = new DSRethinkDBAdapter();
var redisAdapter = new DSRedisAdapter();
var store = new JSData.DS({
  // ...
  strategy: 'fallback',
  fallbackAdapters: ['redis', 'rethinkdb'],
  cacheResponse: false,
  bypassCache: true,
  // ...
});

store.registerAdapter('redis', redisAdapter);
store.registerAdapter('rethinkdb', rethinkdbAdapter, { default: true });

var User = store.defineResource('user');

// For reads you can use Redis as a caching layer
app.get('/user/:id', function (req, res, next) {

  // automatically attempt to find the user with id "1" in Redis 
  // (see "strategy" and "fallbackAdapters")
  // if it's not in Redis then look in Rethinkdb
  return User.find(1).then(function (user) {

    // ensure the user is cached in Redis
    // remember to do this whenever data is updated
    User.update(1, user, { adapter: 'redis' });

    req.status(200).send(user).end();
  }).catch(next);
});