UNPKG

vue-express-mongo-boilerplate

Version:

Express NodeJS application server boilerplate with Mongo and VueJS

341 lines (285 loc) 8.5 kB
# Services ## Service properties Property | Description ----------------| --------------------------- `settings` | Settings of service `actions` | Public actions of service `methods` | Public methods of service `init` | Event handler when the service initialized `ownerChecker` | Method to check the owner of the model `socket` | Websocket settings `graphql` | GraphQL type definitions and resolvers ## Service settings Property | Type | Default | Description ------------------- | -------- | ------------------ | ---------------------------- `name` | String | `null` | Name of the service `version` | Number | `1` | Version of the service. Will be created a versioned path too: `/api/v1/...` `namespace` | String | `""` | Namespace of the service. We use it in the path: `/api/posts` and `/api/v1/posts` `internal` | Boolean | `false` | Is it an internal service (we don't accept requests from HTTP/WS) `rest` | Boolean | `false` | Publish actions of service via HTTP REST API `ws` | Boolean | `false` | Publish actions of service via Websocket `graphql` | Boolean | `false` | Publish actions of service via GraphQL `permission` | String | `C.PERM_LOGGEDIN` | Base permission to access the actions `role` | String | `C.ROLE_USER` | Base role to access the actions `collection` | MongoCollection | `null` | Used collection of the service. `idParamName` | String | `"code"` | Parameter name of the ID field in requests. `/api/posts?code=123` `modelPropFilter` | String | `null` | You can filter the properties in the response JSON. It can be an array with field names or a space-separated string. If empty or `null`, will be expose all properties of the models `modelPopulates` | Object | `null` | Populate schema. The `populateModels` use it to populate the joined fields. It is a key-value pair object. ## toJSON ## Populates ## Permission ## Model resolving ## Actions ## Methods ## REST interface ## Socket.io interface ## GraphQL interface ## Example Example service to handle `posts` collection from DB. ```js { settings: { name: "posts", version: 1, namespace: "posts", rest: true, ws: true, graphql: true, permission: C.PERM_LOGGEDIN, role: "user", collection: Post, modelPropFilter: "code title content author votes voters views createdAt updatedAt", modelPopulates: { "author": "persons", "voters": "persons" } }, actions: { find: { cache: true, handler(ctx) { let filter = {}; if (ctx.params.filter == "my") filter.author = ctx.user.id; else if (ctx.params.author != null) { filter.author = this.personService.decodeID(ctx.params.author); } let query = Post.find(filter); return ctx.queryPageSort(query).exec().then( (docs) => { return this.toJSON(docs); }) .then((json) => { return this.populateModels(json); }); } }, // return a model by ID get: { cache: true, // if true, we don't increment the views! permission: C.PERM_PUBLIC, handler(ctx) { ctx.assertModelIsExist(ctx.t("app:PostNotFound")); return Post.findByIdAndUpdate(ctx.modelID, { $inc: { views: 1 } }).exec().then( (doc) => { return this.toJSON(doc); }) .then((json) => { return this.populateModels(json); }); } }, create: { handler(ctx) { this.validateParams(ctx, true); let post = new Post({ title: ctx.params.title, content: ctx.params.content, author: ctx.user.id }); return post.save() .then((doc) => { return this.toJSON(doc); }) .then((json) => { return this.populateModels(json); }) .then((json) => { this.notifyModelChanges(ctx, "created", json); return json; }); } }, update: { permission: C.PERM_OWNER, handler(ctx) { ctx.assertModelIsExist(ctx.t("app:PostNotFound")); this.validateParams(ctx); return this.collection.findById(ctx.modelID).exec() .then((doc) => { if (ctx.params.title != null) doc.title = ctx.params.title; if (ctx.params.content != null) doc.content = ctx.params.content; return doc.save(); }) .then((doc) => { return this.toJSON(doc); }) .then((json) => { return this.populateModels(json); }) .then((json) => { this.notifyModelChanges(ctx, "updated", json); return json; }); } }, remove: { permission: C.PERM_OWNER, handler(ctx) { ctx.assertModelIsExist(ctx.t("app:PostNotFound")); return Post.remove({ _id: ctx.modelID }) .then(() => { return ctx.model; }) .then((json) => { this.notifyModelChanges(ctx, "removed", json); return json; }); } }, vote(ctx) { ctx.assertModelIsExist(ctx.t("app:PostNotFound")); return this.collection.findById(ctx.modelID).exec() .then((doc) => { // Check user is on voters if (doc.voters.indexOf(ctx.user.id) !== -1) throw ctx.errorBadRequest(C.ERR_ALREADY_VOTED, ctx.t("app:YouHaveAlreadyVotedThisPost")); return doc; }) .then((doc) => { // Add user to voters return Post.findByIdAndUpdate(doc.id, { $addToSet: { voters: ctx.user.id } , $inc: { votes: 1 }}, { "new": true }); }) .then((doc) => { return this.toJSON(doc); }) .then((json) => { return this.populateModels(json); }) .then((json) => { this.notifyModelChanges(ctx, "voted", json); return json; }); }, unvote(ctx) { ctx.assertModelIsExist(ctx.t("app:PostNotFound")); return this.collection.findById(ctx.modelID).exec() .then((doc) => { // Check user is on voters if (doc.voters.indexOf(ctx.user.id) == -1) throw ctx.errorBadRequest(C.ERR_NOT_VOTED_YET, ctx.t("app:YouHaveNotVotedThisPostYet")); return doc; }) .then((doc) => { // Remove user from voters return Post.findByIdAndUpdate(doc.id, { $pull: { voters: ctx.user.id } , $inc: { votes: -1 }}, { "new": true }); }) .then((doc) => { return this.toJSON(doc); }) .then((json) => { return this.populateModels(json); }) .then((json) => { this.notifyModelChanges(ctx, "unvoted", json); return json; }); } }, methods: { /** * Validate params of context. * We will call it in `create` and `update` actions * * @param {Context} ctx context of request * @param {boolean} strictMode strictMode. If true, need to exists the required parameters */ validateParams(ctx, strictMode) { if (strictMode || ctx.hasParam("title")) ctx.validateParam("title").trim().notEmpty(ctx.t("app:PostTitleCannotBeEmpty")).end(); if (strictMode || ctx.hasParam("content")) ctx.validateParam("content").trim().notEmpty(ctx.t("app:PostContentCannotBeEmpty")).end(); if (ctx.hasValidationErrors()) throw ctx.errorBadRequest(C.ERR_VALIDATION_ERROR, ctx.validationErrors); } }, /** * Check the owner of model * * @param {any} ctx Context of request * @returns {Promise} */ ownerChecker(ctx) { return new Promise((resolve, reject) => { ctx.assertModelIsExist(ctx.t("app:PostNotFound")); if (ctx.model.author.code == ctx.user.code || ctx.isAdmin()) resolve(); else reject(); }); }, // Fired when start the service init(ctx) { this.personService = ctx.services("persons"); // Add custom error types C.append([ "ALREADY_VOTED", "NOT_VOTED_YET" ], "ERR"); }, socket: { afterConnection(socket, io) { // Fired when a new client connected via websocket } }, graphql: { query: ` posts(limit: Int, offset: Int, sort: String): [Post] post(code: String): Post `, types: ` type Post { code: String! title: String content: String author: Person! views: Int votes: Int, voters(limit: Int, offset: Int, sort: String): [Person] createdAt: Timestamp updatedAt: Timestamp } `, mutation: ` postCreate(title: String!, content: String!): Post postUpdate(code: String!, title: String, content: String): Post postRemove(code: String!): Post postVote(code: String!): Post postUnvote(code: String!): Post `, resolvers: { Query: { posts: "find", post: "get" }, Mutation: { postCreate: "create", postUpdate: "update", postRemove: "remove", postVote: "vote", postUnvote: "unvote" } } } }; ```