Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

jammin

Package Overview
Dependencies
Maintainers
1
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

jammin - npm Package Compare versions

Comparing version 0.2.1 to 1.0.0

lib/middleware.js

1

index.js
var Jammin = module.exports = {}
Jammin.API = require('./lib/api.js');
Jammin.Client = require('./lib/client.js');
Jammin.middleware = require('./lib/middleware.js');

16

lib/definition.js

@@ -9,3 +9,3 @@ var Async = require('async');

this.label = label;
this.db = model;
this.db = this.model = model;
}

@@ -27,3 +27,8 @@

var find = many ? self.db.find : self.db.findOne;
var run = find.apply(self.db, [query]).select(options.select || '').populate(options.populate || '');
var run = find.apply(self.db, [query, jammin.projection]).select(options.select || '').populate(options.populate || '');
if (jammin.populate) {
if (Array.isArray(jammin.populate)) run.populate(jammin.populate[0], jammin.populate[1]);
else run.populate(jammin.populate);
}
if (jammin.select) run.select(jammin.select);
if (jammin.sort) run.sort(jammin.sort);

@@ -63,5 +68,6 @@ if (jammin.limit) run.limit(jammin.limit);

self.db.findOne(query).exec(function(err, oldDoc) {
if (err) return callback(err);
if (err || !oldDoc) return callback(err, oldDoc);
for (var key in doc) {
oldDoc[key] = doc[key];
oldDoc.markModified(key);
}

@@ -95,4 +101,4 @@ oldDoc.save(callback);

else if (sendResponse && (useMethod === 'get' || useMethod === 'post' || useMethod === 'put')) {
if (many) thing = thing.map(function(t) { return t.toObject(TO_OBJ_OPTIONS) });
else thing = thing.toObject(TO_OBJ_OPTIONS);
if (many) thing = thing.map(function(t) { return t.toJSON(TO_OBJ_OPTIONS) });
else thing = thing.toJSON(TO_OBJ_OPTIONS);
if (options.mapItem) {

@@ -99,0 +105,0 @@ if (many) thing = thing.map(options.mapItem);

{
"name": "jammin",
"version": "0.2.1",
"version": "1.0.0",
"description": "REST API Generator using Express and Mongoose",

@@ -23,2 +23,3 @@ "main": "index.js",

"cors": "^2.7.1",
"mockgoose": "^5.0.10",
"password-hash": "^1.2.2",

@@ -25,0 +26,0 @@ "validator": "^3.39.0"

@@ -0,32 +1,30 @@

# jammin
## Installation
```npm install jammin```
**Note: Jammin is still in development. The API is not stable.**
## About
Jammin is the fastest way to build REST APIs in NodeJS. It consists of:
* A light-weight wrapper around [Mongoose](http://mongoosejs.com/) to expose database operations
* A light-weight module wrapper to expose functions as API endpoints
* A light-weight wrapper around [Mongoose](http://mongoosejs.com/) to perform database operations
* An Express router to link database operations to HTTP operations
Jammin is built for [Express](http://expressjs.com/) and is fully extensible via **middleware** to support things like authentication, sanitization, and resource ownership.
In addition to performing database CRUD, Jammin can bridge function calls over HTTP. If you have a node module that communicates via JSON-serializable data, Jammin allows you to ```require()``` that module from a remote NodeJS client. See the Modules section for an example.
Use ```API.addModel()``` to add an existing Mongoose model. You can attach HTTP routes to each model that will use ```req.params``` and ```req.query``` to query the database and ```req.body``` to update it.
Jammin can also serve a [Swagger](http://swagger.io) specification, allowing your API to link into tools like [Swagger UI](http://petstore.swagger.io/) and [LucyBot](https://lucybot.com)
## Quickstart
## Usage
### Database Operations
Use ```API.define()``` to create Mongoose models. You can attach HTTP routes to each model that will use ```req.params``` and ```req.query``` to query the database and ```req.body``` to update it.
```js
var App = require('express')();
var Mongoose = require('mongoose');
var Jammin = require('jammin');
var API = new Jammin.API('mongodb://<username>:<password>@<mongodb_host>');
var PetSchema = {
var PetSchema = Mongoose.Schema({
name: String,
age: Number
};
});
API.define('Pet', PetSchema);
var Pet = Mongoose.model('Pet', PetSchema);
API.addModel('Pet', Pet);
API.Pet.get('/pets/:name');

@@ -46,34 +44,2 @@ API.Pet.post('/pets');

### Modules (beta)
Use ```API.module()``` to automatically pass ```req.query``` and ```req.body``` as arguments to a pre-defined set of functions.
This example exposes filesystem operations to the API client.
```js
var App = require('express')();
var Jammin = require('jammin');
var API = new Jammin.API();
API.module('/files', {module: require('fs'), async: true});
App.use('/v0', API.router);
App.listen(3000);
```
```bash
> curl -X POST $HOST/v0/files/writeFile?path=hello.txt -d {"data": "Hello World!"}
> curl -X POST $HOST/v0/files/readFile?path=hello.txt
Hello World!
```
Use ```Jammin.Client()``` to create a client of the remote module.
```js
var RemoteFS = new Jammin.Client({
module: require('fs'),
basePath: '/files',
host: 'http://127.0.0.1:3000',
});
RemoteFS.writeFile('foo.txt', 'Hello World!', function(err) {
RemoteFS.readFile('foo.txt', function(err, contents) {
console.log(contents); // Hello World!
});
});
```
## Documentation

@@ -86,3 +52,3 @@

```js
API.Pet.get('/pet/:name');
API.Pet.get('/pets/:name');
API.Pet.getMany('/pets')

@@ -131,29 +97,2 @@ ```

### Modules (beta)
Jammin allows you to expose arbitrary functions as API endpoints. For example, we can give API clients access to the filesystem.
```js
API.module('/files', {module: require('fs'), async: true})
```
Jammin will expose top-level functions in the module as POST requests. Arguments can be passed in-order as a JSON array in the POST body. Jammin also parses the function's toString() to get parameter names, allowing arguments to be passed via a JSON object in the POST body (using the parameter names as keys). Strings can also be passed in as query parameters.
All three of the following calls are equivalent:
```bash
> curl -X POST $HOST/files?path=foo.txt&data=hello
> curl -X POST $HOST/files -d '{"path": "foo.txt", "data": "hello"}'
> curl -X POST $HOST/files -d '["foo.txt", "hello"]'
```
See the Middleware section below for an example of how to more safely expose fs
Jammin also provides clients for exposed modules. This allows you to bridge function calls over HTTP, effectively allowing you to ```require()``` modules from a remote client.
This allows you to quickly containerize node modules that communicate via JSON-serializable data, e.g. to place a particularly expensive operation behind a load balancer, or to run potentially malicious code inside a sandboxed container.
```js
var RemoteFS = new Jammin.Client({
module: require('fs'),
basePath: '/files',
host: 'http://127.0.0.1:3000',
});
```
### Middleware

@@ -168,4 +107,22 @@ You can use middleware to intercept database calls, alter the request, perform authentication, etc.

Change ```req.jammin.arguments``` to alter function calls made to modules.
Jammin also comes with prepackaged middleware to support the following Mongoose operations:
`limit`, `sort`, `skip`, `projection`, `populate`, `select`
#### Examples
Here are three equivalent ways to sort the results and limit how many are returned.
```js
var J = require('jammin').middleware
// The following are all equivalent
API.Pet.getMany('/pets', J.limit(20), J.sort('+name'));
API.Pet.getMany('/pets', J({limit: 20, sort: '+name'}));
API.Pet.getMany('/pets', function(req, res, next) {
req.jammin.limit = 20;
req.jammin.sort = '+name';
next();
})
```
The example below alters ```req.query``` to construct a complex Mongo query from user inputs.

@@ -197,44 +154,32 @@ ```js

var setOwnership = function(req, res, next) {
req.jammin.document.owner = req.user.username;
req.jammin.document.owner = req.user._id;
next();
}
var ownersOnly = function(req, res, next) {
req.jammin.query.owner = {"$eq": req.user.username};
req.jammin.query.owner = {"$eq": req.user._id};
next();
}
API.Pets.get('/pets');
API.Pets.post('/pets', setOwnership);
API.Pets.patch('/pets/:id', ownersOnly);
API.Pets.delete('/pets/:id', ownersOnly);
API.Pet.get('/pets');
API.Pet.post('/pets', setOwnership);
API.Pet.patch('/pets/:id', ownersOnly);
API.Pet.delete('/pets/:id', ownersOnly);
```
You can also use middleware to alter calls to module functions. This function sanitizes calls to fs:
### Manual Calls and Intercepting Results
You can manually run a Jammin query and view the results before sending them to the user. Simply call the operation you want without a path.
Jammin will automatically handle 404 and 500 errors, but will pass the results of successful operations to your callback.
```js
API.module('/files', {module: require('fs'), async: true}, function(req, res, next) {
if (req.path.indexOf('Sync') !== -1) return res.status(400).send("Synchronous functions not allowed");
// Remove path traversals
req.jammin.arguments[0] = Path.join('/', req.jammin.arguments[0]);
// Make sure all operations are inside __dirname/user_files
req.jammin.arguments[0] = Path.join(__dirname, 'user_files', req.jammin.arguments[0]);
next();
});
app.get('/pets', J.limit(20), function(req, res) {
API.Pet.getMany(req, res, function(pets) {
res.json(pets);
})
})
```
### Swagger (beta)
Serve a [Swagger specification](http://swagger.io) for your API at the specified path. You can use this to document your API via [Swagger UI](https://github.com/swagger-api/swagger-ui) or a [LucyBot portal](https://lucybot.com)
If you'd like to handle errors manually, you can also access the underlying model:
```js
API.swagger('/swagger.json');
API.Pet.model.findOneAndUpdate(...);
```
Jammin will automatically fill out most of your spec, but you can provide additional information:
```js
var API = new Jammin.API({
databaseURL: DatabaseURL,
swagger: {
info: {title: 'Pet Store'},
host: 'api.example.com',
basePath: '/api'
}
});
```
## Extended Usage
See the example [Petstore Server](test/petstore-server.js) for other examples.
var FS = require('fs');
var Hash = require('password-hash');
var Mongoose = require('mongoose');
require('mockgoose')(Mongoose);
var App = require('express')();
App.use(require('cors')());
module.exports.listen = function(port) {
module.exports.listen = function(port, done) {
console.log('listening: ' + port);
App.listen(port || 3000);
Mongoose.connect('mongodb://example.com/TestingDB', done);
}
module.exports.dropAllEntries = function(callback) {
API.Pet.db.remove({}, function(err) {
if (err) throw err;
API.User.db.remove({}, function(err) {
if (err) throw err;
callback();
})
})
}
var DatabaseURL = JSON.parse(FS.readFileSync('./creds/mongo.json', 'utf8')).url;
var Jammin = require('../index.js')
var Jammin = require('../index.js'),
J = Jammin.middleware;
var API = new Jammin.API({
databaseURL: DatabaseURL,
swagger: {
info: {title: 'Pet Store', version: '0.1'},
host: 'api.example.com',
basePath: '/api',
securityDefinitions: {
username: { name: 'username', in: 'header', type: 'string'},
password: { name: 'password', in: 'header', type: 'string'}
},
definitions: {
User: {
properties: {
username: {type: 'string'},
}
},
}
}
connection: Mongoose,
});
var UserSchema = {
var UserSchema = Mongoose.Schema({
username: {type: String, required: true, unique: true, match: /^\w+$/},
password_hash: {type: String, required: true, select: false},
}
})

@@ -54,3 +30,3 @@ var vaccSchema = Mongoose.Schema({

var PetSchema = {
var PetSchema = Mongoose.Schema({
id: {type: Number, required: true, unique: true},

@@ -61,4 +37,7 @@ name: String,

vaccinations: [vaccSchema]
}
})
API.addModel('Pet', Mongoose.model('Pet', PetSchema));
API.addModel('User', Mongoose.model('User', UserSchema));
var authenticateUser = function(req, res, next) {

@@ -68,3 +47,3 @@ var query = {

};
API.User.db.findOne(query).select('+password_hash').exec(function(err, user) {
API.User.model.findOne(query).select('+password_hash').exec(function(err, user) {
if (err) {

@@ -83,26 +62,4 @@ res.status(500).json({error: err.toString()})

var SwaggerLogin = {
swagger: {
security: {username: [], password: []},
}
}
API.addModel('Pet', API.mongoose.model('Pet', new Mongoose.Schema(PetSchema)));
API.addModel('User', API.mongoose.model('User', new Mongoose.Schema(UserSchema)));
// Creates a new user.
API.User.post('/users', {
swagger: {
parameters: [{
name: 'body',
in: 'body',
schema: {
properties: {
username: {type: 'string'},
password: {type: 'string'}
}
}
}]
}
}, function(req, res, next) {
API.User.post('/users', function(req, res, next) {
req.jammin.document.password_hash = Hash.generate(req.body.password);

@@ -122,10 +79,3 @@ next();

// Searches pets by name
API.Pet.getMany('/search/pets', {
swagger: {
description: "Search all pets by name",
parameters: [
{name: 'q', in: 'query', type: 'string', description: 'Any regex'}
]
}
}, function(req, res, next) {
API.Pet.getMany('/search/pets', J.sort('+name'), function(req, res, next) {
req.jammin.query = {

@@ -142,3 +92,3 @@ name: { "$regex": new RegExp(req.query.q) }

API.Pet.post('/pets/:id', SwaggerLogin, upsert, authenticateUser, function(req, res, next) {
API.Pet.post('/pets/:id', upsert, authenticateUser, function(req, res, next) {
req.jammin.document.owner = req.user.username;

@@ -150,3 +100,3 @@ req.jammin.document.id = req.params.id;

// Creates one or more new pets.
API.Pet.postMany('/pets', SwaggerLogin, authenticateUser, function(req, res, next) {
API.Pet.postMany('/pets', authenticateUser, function(req, res, next) {
if (!Array.isArray(req.jammin.document)) req.jammin.document = [req.jammin.document];

@@ -167,12 +117,12 @@ req.jammin.document.forEach(function(pet) {

// Changes a pet.
API.Pet.patch('/pets/:id', SwaggerLogin, authenticateUser, enforceOwnership);
API.Pet.patch('/pets/:id', authenticateUser, enforceOwnership);
// Changes every pet that matches the query.
API.Pet.patchMany('/pets', SwaggerLogin, authenticateUser, enforceOwnership);
API.Pet.patchMany('/pets', authenticateUser, enforceOwnership);
// Deletes a pet by ID.
API.Pet.delete('/pets/:id', SwaggerLogin, authenticateUser, enforceOwnership);
API.Pet.delete('/pets/:id', authenticateUser, enforceOwnership);
// Deletes every pet that matches the query.
API.Pet.deleteMany('/pets', SwaggerLogin, authenticateUser, enforceOwnership);
API.Pet.deleteMany('/pets', authenticateUser, enforceOwnership);

@@ -187,4 +137,2 @@ API.router.get('/pet_count', function(req, res) {

API.swagger('/swagger.json');
App.use('/api', API.router);

@@ -7,3 +7,2 @@ var _ = require('lodash');

var SWAGGER_GOLDEN_FILE = __dirname + '/golden/petstore.swagger.json';
var BASE_URL = 'http://127.0.0.1:3333/api';

@@ -68,4 +67,3 @@

before(function(done) {
Petstore.listen(3333);
Petstore.dropAllEntries(done);
Petstore.listen(3333, done);
});

@@ -336,19 +334,2 @@

})
it('should serve swagger docs', function(done) {
Request.get({
url: BASE_URL + '/swagger.json',
json: true
}, function(err, res, body) {
Expect(err).to.equal(null);
if (process.env.WRITE_GOLDEN) {
console.log("Writing new golden file!");
FS.writeFileSync(SWAGGER_GOLDEN_FILE, JSON.stringify(body, null, 2));
} else {
var golden = JSON.parse(FS.readFileSync(SWAGGER_GOLDEN_FILE, 'utf8'));
Expect(body).to.deep.equal(golden);
}
done();
})
})
})
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc