Mongoose resources

Definition

When mongoose is present, you can use yarm.mongoose to serve models as resources.

var Post = mongoose.model(PostSchema)
yarm.mongoose("posts", Post);

You can also use yarm.aggregate to serve aggregates as resources. Contrary to models, aggregates are read-only: only GET requests are supported on aggregate (and sub-property) URLs.

var Post = mongoose.model(PostSchema)

// MongoDB aggregate pipeline
var pipeline = [
  { $project : {
    author : 1,
    tags : 1,
  } },
  { $unwind : "$tags" },
  { $group : {
    _id : "$tags",
    authors : { $addToSet : "$author" }
  } }
];

yarm.aggregate("authorsByTag", Post, pipeline);

Serving collections

GET: retrieving multiple documents

Models and aggregates are served as collections, i.e. yarm will respond with a JSON object containing the total item count and a subset of the collection items.

$ curl http://localhost/rest/posts
{
  "_count": 50,
  "_items": [
    {
      "_id": "507f191e810c19729de860ea",
      "title": "My first post",
      "text": "Hello, World"
    },
    {
      "_id": "507f191e810c19729de62fc7",
      "title": "My first post",
      "text": "Hello again, World"
    }
    ...
  ]
}

Getting specific parts of a collection

By default, yarm returns at most 10 items in collection responses. You can change this default by passing a defaultLimit option to the middleware.

app.use(yarm({ defaultLimit: 100 }));

Clients can also specify an offset and limit when requesting collections. The requested limit will override the default value, and requesting a limit of 0 will make yarm return all items from the collection, starting at the specified offset. In any case, the "_count" property will always return the total item count in the collection.

$ curl http://localhost/rest/posts?limit=1
{
  "_count": 50,
  "_items": [
    {
      "_id": "507f191e810c19729de860ea",
      "title": "My first post",
      "text": "Hello, World"
    }
  ]
}

$ curl http://localhost/rest/posts?skip=49&limit=0
{
  "_count": 50,
  "_items": [
    {
      "_id": "507f191e810c19729d5362b8",
      "title": "My 50th post",
      "text": "This is getting boring..."
    }
  ]
}

Searching for documents

Clients can request documents matching a specific query in a collection using the query request parameter. Here are a few examples overviewing what you can do with queries.

# All posts with title equal to "First post"
curl http://localhost/rest/posts?query=title:First Post

# All posts not written by me
curl http://localhost/rest/posts?query=author!me

# All posts matching the regexp /post/ (make sure the client URL-encodes the
# query parameter)
curl http://localhost/rest/posts?query=title:/post/

# All posts not written by the nsa
curl  http://localhost/rest/posts?query=author!/\.nsa\.gov$/

# Regexps can be made case-insensitive
curl http://localhost/rest/posts?query=title:/post/i

# Logical expression, AND operators have priority over OR operators
curl http://localhost/rest/posts?query=title:/post/ OR text:/hello/i AND isPublic:1

Clients can mix document queries with the skip and limit parameters. The _count property in the returned object will always be the total number of documents matching the query in the collection.

Using a custom query

When serving model resources, you can alter the query used to retrieve documents by setting the query option on a model resource.

yarm.mongoose("posts", Post)
  .set("query", function() {
    return Post.find({ isPublic: true });
  });

Aggregate resources don't support custom queries, as you can already customize the aggregation pipeline.

Sorting collections

When serving model resources, you could use a custom query to sort collections, but you may prefer using the sort option instead.

// Instead of using this:
yarm.mongoose("posts", Post)
  .set("query", function() {
    return Post.find({ isPublic: true }).sort({ date: -1 });
  });

// It is easier to use the sort option
yarm.mongoose("posts", Post)
  .set("query", function() {
    return Post.find({ isPublic: true });
  })
  .set("sort", { date: -1 });

Aggregate resources don't support a sort option, as you can already sort documents in the aggregation pipeline.

POST: adding new documents

Clients can POST new documents to model collections.

$ curl -X POST -d '{"title":"New Post","text":"Whatever..."}' \
  http://localhost/rest/posts

$ curl http://localhost/rest/posts?query=title:New Post
{
  "_count": 1,
  "_items": [
    {
      "_id": "507f191e810c1972967fd7c3",
      "title": "New Post",
      "text": "Whatever..."
    }
  ]
}

By default, a "201 Created" HTTP response is sent to the client when POSTing new documents. This behaviour can be changed by setting the postResponse option to a truthy value; in this case, the created document will be returned to the client.

Serving documents

GET: retrieving single documents

By default, collection documents are accessible by adding the document ObjectID value to the collection URL.

$ curl http://localhost/rest/posts/507f191e810c19729de860ea
{
  "_id": "507f191e810c19729de860ea",
  "title": "My first post",
  "text": "Hello, World"
}

If your documents have a more user-friendly identifier property, you can use the key option to tell yarm.mongoose to use this property instead.

Note that this option is not available for aggregate resources as the aggregation pipeline already allows you to map the _id property to whatever value you want.

yarm.mongoose("posts", Post)
  .set("key", "postId");
$ curl http://localhost/rest/posts/my-first-post
{
  "_id": "507f191e810c19729de860ea",
  "postId": "my-first-post",
  "title": "My first post",
  "text": "Hello, World"
}

You can change the way yarm returns documents by using mongoose toObject option. This option can be set on the model resource directly. Refer to the mongoose documentation for more information on how this option works.

Again, this opton is not available for aggregate resources, as the aggregation pipeline already allows you to tailor documents the way you want.

yarm.mongoose("posts", Post)
  .set("toObject", {
    // Include virtual properties in output
    virtuals: true,

    // Hide _id property
    toObject: function(doc, ret, options) {
      delete ret._id;
    }
  });

When the toObject option is set on the model resource, it will apply to responses to both collection requests and document requests. You can specify a different toObject option for sub-resources, refer to Setting options for more information.

Before returning documents, yarm adds a _request property to them with the Express request object. This allows using the request for example in a virtual property in your model.

DELETE: removing documents

Clients can remove documents by sending DELETE requests on the document URL.

$ curl -X DELETE http://localhost/rest/posts/my-first-post
$ curl -i http://localhost/rest/posts/my-first-post
HTTP/1.1 404 Not found

PUT and PATCH: updating documents

Clients can update documents by sending PUT or PATCH requests on the document URL. For now, both methods behave as a PATCH request, that is, they update all fields that are present in the request body, without touching other fields.

$ curl -X PATCH -d '{"title":"New title"}' \
  http://localhost/rest/posts/507f191e810c19729de860ea
$ curl http://localhost/rest/posts/507f191e810c19729de860ea
{
  "_id": "507f191e810c19729de860ea",
  "title": "New title",
  "text": "Hello, World"
}

Serving document properties

GET: retrieving document properties

As with native resources, clients can request any document property (or subproperty).

$ curl http://localhost/rest/posts/507f191e810c19729de860ea/title
My first post

$ curl http://localhost/rest/posts/507f191e810c19729de860ea/tags
["homepage", "public", "hello"]

When your model schema includes document arrays, they are served as collections. Clients can use the skip, limit and query request parameters with those collections as well.

var PostSchema = new mongoose.Schema({
  title: String,
  text: String,
  comments: [{
    author: String,
    text: String
  }]
})

var Post = mongoose.model("posts", PostSchema);

yarm.mongoose("posts", Post);
$ curl http://localhost/rest/posts/my-post/comments
{
  "_count": 3,
  "_items": [
    {
      _id: "507f191e810c19729f526a7",
      author: "Alice",
      text: "First !"
    },
    ...
  ]
}

$ curl http://localhost/rest/posts/my-post/comments?query=author:Alice
{
  "_count": 1,
  "_items": [
    {
      _id: "507f191e810c19729f526a7",
      author: "Alice",
      text: "First !"
    }
  ]
}

$ curl http://localhost/rest/posts/my-post/comments/507f191e810c19729f526a7
{
  _id: "507f191e810c19729f526a7",
  author: "Alice",
  text: "First !"
}

$ curl http://localhost/rest/posts/my-post/comments/507f191e810c19729f526a7/text
First !

When your model schema contains references to other collections, you may want to adjust the query option on the mongoose resource so that mongoose populates those references.

var PersonSchema = new mongoose.Schema({ ... });
var Person = mongoose.model("person", PersonSchema);

var CommentSchema = new mongoose.Schema({ ... });
var Comment = mongoose.model("comment", CommentSchema);

var PostSchema = new mongoose.Schema({
  author: { type: mongoose.Schema.types.ObjectId, ref: "person" },
  comments: [{ type: mongoose.Schema.types.ObjectId, ref: "comment" }]
});
var Post = mongoose.model("post", PostSchema);

yarm.mongoose("posts", Post)
  .set("query", function() {
    return Post.find().populate("author comments");
  });

DELETE: removing document properties

Clients can remove document properties or sub-properties by sending a DELETE request on the property URL.

$ curl -X DELETE http://localhost/posts/my-first-post/comments/507f191e810c19729f526a7

PUT and PATCH: updating document properties

Clients can update document properties or sub-properties by sending PUT or PATCH requests on the property URL. If the request body contains a _value field, it will be used instead. This allows passing values that would otherwise not be valid JSON (strings, numbers, booleans, ...).

$ curl -X PATCH -d '{"_value":"New title"}' \
  http://localhost/rest/posts/507f191e810c19729de860ea/title

$ curl http://localhost/rest/posts/507f191e810c19729de860ea
{
  "_id": "507f191e810c19729de860ea",
  "title": "New title",
  "text": "Hello, World"
}

POST: adding sub-documents to document arrays

When your schema contains a document array, clients can add new sub-documents by sending POST requests on the document array URL.

$ curl -X POST -d '{"author":"Bob","text":"This is a nice post !"}' \
  http://localhost/rest/posts/507f191e810c19729de860ea/comments

By default, a "201 Created" HTTP response is sent to the client when POSTing new sub-documents. This behaviour can be changed by setting the postResponse option to a truthy value; in this case, the created sub-document will be returned to the client.