Skip to content

Instantly share code, notes, and snippets.

@gpDA
Created September 19, 2021 20:36
Show Gist options
  • Save gpDA/3e4e87381d1109b3c1245420561c9002 to your computer and use it in GitHub Desktop.
Save gpDA/3e4e87381d1109b3c1245420561c9002 to your computer and use it in GitHub Desktop.
// Things to go over
// 1 - mongodb (mongoose ORM) list of strings & default
// 2 - router middleware (koa)
// 3 - CRUD (with simple validation) write / read /remove / update
////\\\\
// models/post.js
////\\\\
const mongoose = require('mongoose');
const { Schema } = mongoose;
const Post = new Schema({
title: String,
body: String,
tags: [String], // list of strings
publishedDate: {
type: Date,
default: new Date() // current date default
////\\\\
// api/posts/index.js
////\\\\
const Router = require('koa-router');
const postsCtrl = require('./posts.ctrl');
const posts = new Router();
posts.get('/', postsCtrl.list);
posts.get('/:id', postsCtrl.checkObjectId, postsCtrl.read);
posts.post('/', postsCtrl.write);
posts.delete('/:id', postsCtrl.checkObjectId, postsCtrl.remove);
posts.patch('/:id', postsCtrl.checkObjectId, postsCtrl.update);
module.exports = posts;
}
});
module.exports = mongoose.model('Post', Post);
////\\\\
// api/posts/posts.ctrl.js
////\\\\
const Post = require('models/post');
const Joi = require('joi');
const { ObjectId } = require('mongoose').Types;
exports.checkObjectId = (ctx, next) => {
const { id } = ctx.params;
// validation
if (!ObjectId.isValid(id)) {
ctx.status = 400; // 400 Bad Request
return null;
}
return next(); // return next to execute the next ctx.body
};
/*
POST /api/posts
{ title, body, tags }
*/
exports.write = async (ctx) => {
// required validation
const schema = Joi.object().keys({
title: Joi.string().required(),
body: Joi.string().required(),
tags: Joi.array().items(Joi.string()).required()
});
// validation
const result = Joi.validate(ctx.request.body, schema);
// error
if (result.error) {
ctx.status = 400;
ctx.body = result.error;
return;
}
const { title, body, tags } = ctx.request.body;
// new Post instance
const post = new Post({
title, body, tags
});
try {
await post.save(); // save to database
ctx.body = post; // return saved data
} catch (e) {
// database error
ctx.throw(e, 500);
}
};
/*
GET /api/posts
*/
exports.list = async (ctx) => {
// default page: 1
const page = parseInt(ctx.query.page || 1, 10);
const { tag } = ctx.query;
const query = tag ? {
tags: tag // filter by tag from tags list
} : {};
// if given page number is wrong
if (page < 1) {
ctx.status = 400;
return;
}
try {
const posts = await Post.find(query)
.sort({ _id: -1 })
.limit(3)
.skip((page - 1) * 3)
.lean()
.exec();
const postCount = await Post.countDocuments(query).exec();
const limitBodyLength = post => ({
...post,
body: post.body.length < 350 ? post.body : `${post.body.slice(0, 350)}...`
});
ctx.body = posts.map(limitBodyLength);
// if last page
// set the `last page` as a response header
ctx.set('Last-Page', Math.ceil(postCount / 3));
} catch (e) {
ctx.throw(500, e);
}
};
/*
GET /api/posts/:id
*/
exports.read = async (ctx) => {
const { id } = ctx.params;
try {
const post = await Post.findById(id).exec();
// post not exists
if (!post) {
ctx.status = 404;
return;
}
ctx.body = post;
} catch (e) {
ctx.throw(e, 500);
}
};
/*
DELETE /api/posts/:id
*/
exports.remove = async (ctx) => {
const { id } = ctx.params;
try {
await Post.findByIdAndRemove(id).exec();
ctx.status = 204;
} catch (e) {
ctx.throw(e, 500);
}
};
/*
PATCH /api/posts/:id
{ title, body, tags }
*/
exports.update = async (ctx) => {
const { id } = ctx.params;
try {
const post = await Post.findByIdAndUpdate(id, ctx.request.body, {
new: true
// return updated object
}).exec();
// if no post exists
if (!post) {
ctx.status = 404;
return;
}
ctx.body = post;
} catch (e) {
ctx.throw(e, 500);
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment