Skip to content

Instantly share code, notes, and snippets.

@anatoliychakkaev
Last active December 10, 2015 17:49
Show Gist options
  • Save anatoliychakkaev/4470669 to your computer and use it in GitHub Desktop.
Save anatoliychakkaev/4470669 to your computer and use it in GitHub Desktop.
Experimental compound events + common controllers API.

About

This is example of using compound API with any express application. Every part of compound could be accessed via API. Directories structure is optional: you can do everything in single file using compount events API.

Insallation

npm install compound jugglingdb express

Running

node simple-crud-app.js

Usage

use curl to access /books API endpoints (as usual resource)

var co = require('compound');
var Compound = co.Compound;
var db = require('jugglingdb');
var express = require('express');
// create regular express app
var server = express();
server.configure(function() {
server.use(express.bodyParser());
server.use(express.cookieParser('s3cr3t'));
server.use(express.session({secret: '5ecr3t'}));
server.use(express.methodOverride());
server.use(server.router);
});
// create compound
new Compound(server, __dirname)
// register models
.on('models', function registerSchema(models) {
// same as ./db/schema.{js, coffee}
var schema = new db.Schema('memory');
var Book = models.Book = schema.define('Book', {
title: String,
author: String,
ISBN: { type: String, index: true }
});
Book.validatesPresenceOf('ISBN');
Book.validatesUniquenessOf('ISBN');
})
// register controller (views, helpers)
.on('structure', function initializeAppStructure(structure) {
// create crud controller for Book model using common controller 'crudJSON'
structure.controllers.books = co.controllers.crudJSON('Book');
// structure object also used for views, helpers and models
// normally at this stage we load app/{models, controllers, helpers} directories
})
// draw routes
.on('routes', function drawRoutes(map) {
// here we can draw some routes on our map
map.resources('books');
// normally this done in config/routes.{js, coffee}
});
// start app
var host = '0.0.0.0';
var port = 3000;
server.listen(port, host, function() {
console.log('Compound server listen on %s:%d', host, port);
});
// check todos-app.js for more advanced example
var co = require('compound');
var Compound = co.Compound;
var db = require('jugglingdb');
var express = require('express');
// create regular express app
var server = express();
server.configure(function() {
server.use(express.bodyParser());
server.use(express.cookieParser('s3cr3t'));
server.use(express.session({secret: '5ecr3t'}));
server.use(express.methodOverride());
server.use(server.router);
});
// create compound
new Compound(server, __dirname)
// register models
.on('models', function registerSchema(models) {
// same as ./db/schema.{js, coffee}
var schema = new db.Schema('memory');
var Task = models.Task = schema.define('Task', {
description: String
});
Task.validatesPresenceOf('description');
var List = models.List = schema.define('List', {
name: String
});
List.validatesPresenceOf('name');
List.hasMany(Task, {as: 'tasks', foreignKey: 'listId'});
Task.belongsTo(List, {as: 'list', foreignKey: 'listId'});
})
// register controller (views, helpers)
.on('structure', function initializeAppStructure(structure) {
// create crud controller for Task and List models using common controller 'crudJSON'
var TasksController, ListsController;
with (structure) {
TasksController = controllers.tasks = co.controllers.crudJSON('Task', ParentController);
ListsController = controllers.lists = co.controllers.crudJSON('List');
}
// structure object also used for views, helpers and models
// normally at this stage we load app/{models, controllers, helpers} directories
function ParentController(init) {
init.before(function loadList(c) {
c.List.find(c.params['list_id'], function (err, list) {
if (err) {
return c.send({code: 500, error: err});
}
this.list = list;
c.next();
}.bind(this));
});
}
TasksController.prototype.create = function create(c) {
this.list.tasks.create(c.body.Task, function (err, task) {
if (err) {
return c.send({code: 500, error: task.errors || err});
}
c.send({
code: 200,
task: task
});
});
};
TasksController.prototype.index = function index(c) {
this.list.tasks(function (err, tasks) {
if (err) {
return c.send({code: 500, error: err});
}
c.send({
code: 200,
tasks: tasks
});
});
};
})
// draw routes
.on('routes', function drawRoutes(map) {
// here we can draw some routes on our map
map.resources('lists', function (list) {
list.resources('tasks');
});
// normally this done in config/routes.{js, coffee}
});
// start app
var host = '0.0.0.0';
var port = 3000;
server.listen(port, host, function() {
console.log('Compound server listen on %s:%d', host, port);
});

Usage log for simple crud app

Error handling

~: ) curl -ki -X POST -F 'Book[title]=asdad' http://localhost:3000/books.json
HTTP/1.1 100 Continue

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 78
Set-Cookie: connect.sid=s%3ASOiJMAnwkEi3heS5gxiIPbLD.0eMVx6lQI9xairmgJqs6ziEHWJxNnk652EU%2Flz4Y5w4; Path=/; HttpOnly
Date: Mon, 07 Jan 2013 00:04:58 GMT
Connection: keep-alive

{
  "code": 500,
  "error": {
    "ISBN": [
      "can't be blank"
    ]
  }
}

Create record

~: ) curl -ki -X POST -F 'Book[ISBN]=9999999'  http://localhost:3000/books.json
HTTP/1.1 100 Continue

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 108
Set-Cookie: connect.sid=s%3A36hoR5HU77RdYKY4SVWgxe8E.sbUygfx65pk54drFJfhw8qaZEOWzzLXkJjbhr574zMU; Path=/; HttpOnly
Date: Mon, 07 Jan 2013 00:08:06 GMT
Connection: keep-alive

{
  "code": 200,
  "book": {
    "title": null,
    "author": null,
    "ISBN": "9999999",
    "id": 1
  }
}

Get non-existent record

~: ) curl -ki http://localhost:3000/books/2
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 17
Set-Cookie: connect.sid=s%3ArQ8B3n4efKIgNQhtHPJw00l7.ae51UWTUjqW9tZgr8YvFKhU41fSQYN%2BRX8mf98XItcU; Path=/; HttpOnly
Date: Mon, 07 Jan 2013 00:08:25 GMT
Connection: keep-alive

{
  "code": 404
}

Get record#1

~: ) curl -ki http://localhost:3000/books/1
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 108
Set-Cookie: connect.sid=s%3AW3kD4%2BBv9Ntz1LIpmJioWyXe.qYpyY7AhW2sX8tDL0p7U2giBDieCgue%2B6FLr4ET33kE; Path=/; HttpOnly
Date: Mon, 07 Jan 2013 00:08:34 GMT
Connection: keep-alive

{
  "code": 200,
  "book": {
    "title": null,
    "author": null,
    "ISBN": "9999999",
    "id": 1
  }
}

Get all records

~: ) curl -ki http://localhost:3000/books
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 129
Set-Cookie: connect.sid=s%3AOftlE6DkzhAGESZ5LwXcZ5ed.BpB7IH%2FQVcP%2BxnfBX3vAOZhfbY6Fu6sw%2B3G%2Bc4ZAJwY; Path=/; HttpOnly
Date: Mon, 07 Jan 2013 00:08:49 GMT
Connection: keep-alive

{
  "code": 200,
  "books": [
    {
      "title": null,
      "author": null,
      "ISBN": "9999999",
      "id": 1
    }
  ]
}

Update record

~: ) curl -ki -X PUT -F 'Book[ISBN]=9999998'  http://localhost:3000/books/1
HTTP/1.1 100 Continue

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 108
Set-Cookie: connect.sid=s%3AKIWWyM8yiOCPQWmPeFlElTl9.4FyP3sz5zjQA9V5xsaZ6Jg4dyAXapEkcsjJRi1hVVt4; Path=/; HttpOnly
Date: Mon, 07 Jan 2013 00:13:00 GMT
Connection: keep-alive

{
  "code": 200,
  "book": {
    "title": null,
    "author": null,
    "ISBN": "9999998",
    "id": 1
  }
}

Delete record

~: ) curl -ki -X DELETE  http://localhost:3000/books/1
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 8
Set-Cookie: connect.sid=s%3AhTw%2BWjbPDH6FUjEiGYPuC%2FKs.BfRHFVxUihUtutkR6moGBZhB9quOn%2Bt0lQ%2F1P%2FY0%2B%2FM; Path=/; HttpOnly
Date: Mon, 07 Jan 2013 00:13:55 GMT
Connection: keep-alive

{
  "code": 200
}

Usage log for todos app

Create first list

19:31:32 anatoliy@mac ~/projects/compound (master) 
~: ) curl -X POST -F 'List[name]=test' http://localhost:3000/lists{
  "code": 200,
  "list": {
    "name": "test",
    "id": 1
  }
}

Add task to first list

19:31:50 anatoliy@mac ~/projects/compound (master) 
~: ) curl -X POST -F 'Task[description]=do something' http://localhost:3000/lists/1/tasks
{
  "code": 200,
  "task": {
    "description": "do something",
    "id": 1,
    "listId": 1
  }
}

Add another list

19:31:51 anatoliy@mac ~/projects/compound (master) 
~: ) curl -X POST -F 'List[name]=test2' http://localhost:3000/lists{
  "code": 200,
  "list": {
    "name": "test2",
    "id": 2
  }
}

Add two tasks to second list

19:32:01 anatoliy@mac ~/projects/compound (master) 
~: ) curl -X POST -F 'Task[description]=do something else' http://localhost:3000/lists/2/tasks
{
  "code": 200,
  "task": {
    "description": "do something else",
    "id": 2,
    "listId": 2
  }
}

19:32:13 anatoliy@mac ~/projects/compound (master) 
~: ) curl -X POST -F 'Task[description]=do something else again' http://localhost:3000/lists/2/tasks
{
  "code": 200,
  "task": {
    "description": "do something else again",
    "id": 3,
    "listId": 2
  }
}

Retrieve tasks for lists

19:32:17 anatoliy@mac ~/projects/compound (master) 
~: ) curl http://localhost:3000/lists/1/tasks
{
  "code": 200,
  "tasks": [
    {
      "description": "do something",
      "id": 1,
      "listId": 1
    }
  ]
}

19:32:35 anatoliy@mac ~/projects/compound (master) 
~: ) curl http://localhost:3000/lists/2/tasks
{
  "code": 200,
  "tasks": [
    {
      "description": "do something else",
      "id": 2,
      "listId": 2
    },
    {
      "description": "do something else again",
      "id": 3,
      "listId": 2
    }
  ]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment