the.rest
A flexible REST backend with an ORM like frontend for Mongoose, working well in React.
the.rest sets up all REST routes automatically and corresponding frontend classes that consume them but looks like normal classes to you.
Installation
This module is meant to be used together with Express and Mongoose so if you haven't done so already:
npm install express
npm install mongoose
Then:
npm install the.rest
Backend setup
Here is an example of fairly typical backend setup
index.js
// Modulesconst path = ;const express = ;const mongoose = ;const theRest = ; // Connect to MongoDB via Mongoosemongoose;const db = mongooseconnection; // Create an Express serverconst app = ; // ..and install the.rest as middleware// Arguments/configuration:// 1) The express library// 2) The base route for the REST api to create// 3) The path to a folder with mongoose-models // Please Note: This path must be absoluteconst pathToModelFolder = path;app; // Add other middleware you might need (express.static etc) // Listen on port 5000app;
Create the folder mongoose-models
Create a folder named mongoose-models and a file for each mongoose model. Each file should export a mongoose model. It should look something like this:
const mongoose = ; const modelName = 'Cat';const schema = name: String; moduleexports = mongoose;
If in a React/Vue etc. development environment - make proxy settings for yor node server
React: Proxying API Requests in development
Vue: Devserver proxy
Frontend setup
Old style script include
If you are using plaing old JavaScript (no import-statements/build environments) you can include the frontend part of the.rest like this:
You will now have a global object called REST. Each Mongoose.model will be available as a property - so if you have created a model called Cat and a model called Dog, their frontend equivalents would be available as REST.Cat, REST.Dog etc.
If you want individual variables just use a destructuring assignment:
const Cat Dog = REST;
Using import
If you are in an environment where you import dependencies using import do:
;
Each Mongoose.model will be available as a property - so if you have created a model called Cat and a model called Dog, their frontend equivalents would be available as REST.Cat, REST.Dog etc.
Named imports
You can also use named imports:
;
Usage
the.rest makes it really easy to find, save and delete Mongoose instance from your frontend code.
The API is optimized to be used together with await (inside async functions).
Creating things
Creating and saving a new instance is simple:
// Create a new catlet g = name: 'Garfield';// save it to the db (the properrty _id is also added)await g;
Finding things
All instances
Find all Cats:
let c = await Cat
A single one
let c = await Cat;
Shorthand for find by id is:
let theCat = await Cat;
Any type of query
Any query you can use with Mongo/Mongoose can be used:
// Fin all cats starting with 'gar' in their namelet cats = await Cat;
Use any of the extra methods in Mongoose queries
Mongoose has a lot of extra methods for controlling queries (select, sort, limit, populate etc).
Most of them will work from the frontend with the.rest. You can use the same syntax as in normal Mongoose (writing method chains), execept that you leave out the exec call.
A typical example of Mongoose syntax (backend):
await Cat;
The same thing witten in the.rest syntax on the frontend:
await Cat;
Alternate syntax
If you are targeting old browsers/Internet Explorer, that do not support the Proxy object, you have to use an alternate syntax, where you replace the method chains with a second argument:
await Cat;
Updating things
You use save for updates as well as for creation.
let theCat = await Cat;theCatname += ' Supercat';await theCat;
Deleting things
let theExCat = await Cat;await theCat;
Saving many things at once
The.rest uses a special array subclass that have the methods save and delete - and lets you save/update/delete several instances at once.
Create some new cats
// Create the catslet cats = name: 'A' name: 'B' name: 'C';// Save them all at onceawait cats;
Update all cats at once
// Find the catslet cats = Cats;// Update their namescats;// Save them all at onceawait cats;
Delete many cats at once
// Find the catslet cats = Cats;// Delete them all at onceawait cats;
Adding methods to the classes
If you want a dog who can bark and sit you simply add the methods to the Dog class like this:
Object;
Population
Population with Mongoose is the equivalent of joins in SQL. When you use the.rest you can add the parameter populateRevive to revive populated fields as real the.rest frontend classes.
Given that we have the models Elephant and Tiger and favoriteTiger: {type: mongoose.Schema.Types.ObjectId, ref: 'Tiger'} in the Elephant schema:
// Delete all tigers and elephantsawait await Tiger;await await Elephant; // Create a tigerlet tigger = name: 'Tigger' ;await tigger;console; // Create an elephant that likes Tiggerlet dumbo = name: 'Dumbo' favoriteTiger: tigger;await dumbo;console; // All elephants populated with tigerslet elephants = await Elephant ; console;
Note: If you want to populate several fields, then call populate with a a space delimited string and populateRevive with an array of classes.
ACL (Access Control List) - protect certain routes/actions
If you want to protect certain routes/actions, based on user priviliges or other considerations you can do so by providing a fourth parameter - a function - to the.rest when you setup your backend.
Backend
// See "Backend setup" above for details about basic setup // My ACL function
The acl function recieves an info object and the Express request object. It will be called for each request.
Note: If you choose to return something (preferably a string) it means you are not letting the request through.
You can combine this with modules such as express-session to read what user and user priviliges apply from req.session, but in the example above we simply do not allow population of Elephants regardless of user.
The info object
The info object has the following structure:
route: 'elephants' requestMethod: 'GET' modelName: 'Elephant' model: Model Elephant query: name: /Dum/i extras: populate: 'favoriteTiger'
You get the base/entity route, the request method, the name of the mongoose model, the actual mongoose model object, the query and the extras (i.e. sort, limit, select, populate etc).
This means you don't have to parse this yourself from req.url
Frontend
Acl is "invisible"/transparent by default on the frontend - you simply get empty answers when acl kicks in. But if you want to you can register a listener to pick up the acl messages from the backend:
Elephant { console;};
To unregister:
delete Elephantacl;
Note: The listener is just a property containing a function. If you want to be able to register several listeners to the same class, build your own event registration system based on this fact.
Adding non-mongoose based routes
Sometimes you need to write special routes yourself on the backend, that are not based on Mongoose-models. A common example would be routes for login (- could be done asm POST /api/login logs in, DELETE /api/login logs out and GET /api/login checks who is logged in?).
So in the backend you would write these routes by hand, as opposed to automatically created REST routes from the.rest. However you might still want to use a the.rest based class on the frontend.
You can send a fifth parameter to the rest to do this! It should be an object where the keys are routes and their values are the name of the 'model'/class you want to be created on the front-end.
app;
Change log
- 1.0.0 - 1.0.7 Early additions and bug fixes
- 1.0.8 - PopulateRevive was introduced
- 1.0.9 - Acl added and the RESTClientArray class subclassed for each entity.
- 1.0.10 - 10.0.12 - Minor changes to README.
- 1.0.13 - Explanation of the acl info object added to README.
- 1.0.14 - Minor changes to README.
- 1.0.15 - Not sending res to acl anymore
- 1.0.16 - Minor changes to README.
- 1.0.17 - Getting rid of Express as a dependency (now a first argument to middleware conf)
- 1.0.18 - 10.0.19 - Fixing bug/typo that made 1.0.17 unusable
- 1.0.20 - 10.0.22 - Minor changes to README.
- 1.0.23 - Reviving regexps on backend without $regex wrapper property
- 1.0.24-1.0.26 - Introducing method chain syntax
- 1.0.27 - Temporarily reverting method chain syntax
- 1.0.28 - Reintroducing the method chain syntax
- 1.0.29 - Minor changes to README.
- 1.0.30 - Added possibility to add frontend classes that do not have Mongoose/the.rest backend.
- 1.0.31 - Fixed a bug in DELETE routes
- 1.0.32 - Bugfix populate... TODO: findOne does not work with method chains...