achilles v1.0.0
achilles
A lightweight Model-View-Controller (MVC) library for building beautiful database-backed APIs. Good web applications are built on good APIs, but that doesn't mean hours should be wasted away while a web application that binds a REST API to the database and covers validation, authentication and pagination is crafted. Achilles crafts an API from the database schema:
var achilles = require("achilles");
var mongodb = require("achilles-mongodb");
var db = new mongodb.Connection("mongodb://127.0.0.1:27017/test");
var Book = achilles.Object.extend({
title:String,
author:String,
rating:Number,
datePublished:Date,
readership:[String]
});
db.register(Book, "books");
var BookService = new achilles.Service(Book); // Instance of express.Router
BookService.listen(8080);
Thus you have a fully functioning REST API:
OPTIONS /
HEAD /
GET /
POST /
HEAD /:id
GET /:id
PUT /:id
DELETE /:id
Installation
npm install achilles --save
Express
achilles.Service
is secretly just an instance of an express.Router
, so to use it in any Express application:
app.use("/api", new achilles.Service(model));
Comprehensive Overview
Whether the web apps or native apps are the future, there is something they both rely on. They rely on an API to communicate with the server. A JSON HTTP REST API to be precise. JSON
is the format in which objects are encoded. HTTP
is the protocol by which messages are sent. REST
is the schema endpoints. Achilles was built because doing all of this was difficult, time-consuming and tedious. What made it worse was performing validation, authentication and pagination. Validation is making sure the data is in the correct format. Authentication is making sure that the user is only allowed to perform the actions he/she has been permitted to do. Pagination is giving the client only the items requested, so page-based interfaces can be created. The idea was simple why not create something that would take in a database schema and create an API around that, that will handle all of those things, so you can spend most of your time working on building the client-side web/native application.
It's all possible by introducing a strong-typed Object-Oriented Programming system, like in Java or C++. By default JavaScript is weak-typed, so variables can be of any data type, but it is no use assigning a string to an age
property! Achilles sorts all of that out.
Here's the cool thing though: Achilles also works on client-side JavaScript. What does that mean? Well if you're making a web app server-side classes, client-side. Also on the client-side data is cached (in LocalStorage/IndexedDB/WebSQL) so it doesn't have to be constantly reloaded.
Objects
Objects, whose foundation is achilles.Object
, are based on Mongoose schemata. To create your own class, subclass achilles.Object
, by usomg the .extend()
static method. When you subclass an object all properties, methods and static methods are inherited unless overriden. The .extend()
method takes an object that map property names to classes. To describe an array of a class, put the class in square brackets:
var Person = achilles.Object.extend({
name: String,
age:Number,
children: [People]
});
var Zed = new Person({
name: "Zed"
});
Methods
Methods are added like normal using prototype
and static methods are assigned directly onto the constructor:
Person.prototype.howManyChildren = function() {
return this.children.length;
}
Person.createLucy = function() {
return new Person({
name:"Lucy"
});
}
Constructors
achilles.Object
has an un-overidable default constructor, that takes in an object containing all the default properties of an object. However if a method initialise
exists, that constructor will call that method to initialise the object.
Events
achilles.Object
extends events.EventEmitter
, therefore you can listen for change
events and for events to a specific key, by change:key
:
var person = new Person({
name:"Chloe"
});
person.on("change", function(key) {
console.log("The property " + key + " was changed");
});
person.age = 5;
// "The property age was changed"
person.on("change:name", function() {
console.log("Name change!!!");
});
person.name = "Rachel";
// "The property name was changed"
// "Name change!!!"
Models
To instantiate a connection to a data source you must use the appropriate adapter to that data source. For example, if you are trying to connect to a REST API, especially if it is created by achilles.Service
, you can use achilles.Connection
. This is most probably for client-side use. For server-side use there is a MongoDB adapter. To add methods and static methods such as get
, save
and del
, you must register your class with the database, e.g.:
var db = new achilles.Connection("/api");
db.register(Person, "people");
db.register(model, descriptor)
takes two arguments. The first one is the model, and the second is a description (conventionally a pluralised form of the class name), which is used in some adapters, such as the MongoDB, to specify the sub-database/collection where the documents/records of the specific class are stored, but always in the permissions system, which is covered later and ensures users can only perform operations, to which they have been given permission to complete.
Querying the database
To query the database use the get(options, cb)
static method, where cb
if a function of the signature (err, docs)
. By default docs
is an array of all docs. To limit how many docs
are sent specify a limit
and a skip
in options
.
Person.get({limit: 10, skip: 10}, function(err, docs) {
if(err) {
console.log("There was an error: " + err);
} else {
console.log(docs); // Docs will contain records 11-20 (10 in total)
}
});
The id property
To ensure consistency, the id
property is always used and always returns a String representation of the id
used in the database, regardless of how it is stored in the database.
Get by id
To get a model by id use the getById(id, cb)
static method, where cb
is a function of the signature (err, doc)
.
Person.getById("13532FSDFDS", function(err, doc) {
if(err) {
console.log"There was an error: " + err);
} else {
console.log(doc.id); // "13532FSDFDS"
console.log(doc.name);
}
});
Delete by id
To delete a model by id use the getById(id, cb)
static method, where cb
is a function of the signature (err)
.
Person.delById("13532FSDFDS", function(err) {
if(err) {
console.log("There was an error: " + err);
} else {
console.log("Document was successfully deleted");
}
});
Saving a model
To save a model use the save(cb)
instance method, where cb
is a function of the signature (err, doc)
.
Deleting a model
To delete a model use the del(cb)
instance method, where cb
is the function of the signature (err)
.
Services
achilles.Service(model)
takes in one argument, that is the model of which to create an API. The API has several endpoints. All endpoints use JSON as the format.
OPTIONS /, OPTIONS /:id
Returns all the possible methods at that endpoint in the Allow
header.
HEAD /, HEAD /:id
Equivalent to a GET
request but only returns HTTP headers, not the body. Useful in getting the Content-Length
of something for example.
GET /
Returns all the items in the database, that match the criteria in the where
parameter. If there isn't one, then it will return all the documents.
E.g.:
/?where={age:{$lt:4}}
The where
parameter uses the MongoDB API syntax.
Pagination
To request a certain number of items, use the HTTP Range header:
Range: items=0-9
GET /:id
Returns a given record in the database, by its id.
POST /
Creates a new record in the database. It accepts a JSON body for the contents. Performs validation.
PUT /:id
Update a record in the database, by its id. It will completely overwrite the existing contents with the JSON body. Performs validation. Idempotent.
PATCH /:id
Updates a record in the database, by its id. It accepts a JSON PATCH body containing only the changes. It does not completely overwrite the existing contents, but only applies the updates. Performs validation.
DELETE /:id
Delete a record in the database, by its id. Idempotent.
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago