Finally, we will create controller methods for the various actions we want our application to be able to carry out. These include the following:
- Index: View all blog posts
- Create: View a form to create a new blog post
- Store: Save a new blog post to the database
- Show: View a single blog post
- Edit: View a form to edit a blog post
- Update: Update a blog post in the database
Let's create a controller file for our post actions name PostController in the controllers folder, and insert the following contents into it:
// ./controllers/PostController.js
const Post = require('../models/Post');
module.exports = {
async index(ctx) {
const posts = await Post.find()
.populate('author');
ctx.state.posts = posts;
ctx.state.title = 'Home';
await ctx.render('index');
},
async create(ctx) {
ctx.state.title = 'Create Post';
await ctx.render('post/create');
},
async store(ctx) {
const { body } = ctx.request;
const postData = {
...body,
author: ctx.session.user,
image: 'https://picsum.photos/300/?random'
};
const post = await new Post(postData).save();
ctx.redirect(`/post/${post.id}`);
},
async show(ctx) {
const { id } = ctx.params;
try {
const post = await Post.findById(id).populate('author');
ctx.state.post = post;
ctx.state.title = post.title;
} catch(e) {
ctx.throw(404, "Post not found");
}
await ctx.render('post/show');
},
async edit(ctx) {
const { id } = ctx.params;
try {
const post = await Post.findById(id).populate('author');
ctx.state.post = post;
ctx.state.title = `Edit Post - ${post.title}`;
} catch(e) {
ctx.throw(404, "Post not found");
}
await ctx.render('post/edit');
},
async update(ctx) {
const { id } = ctx.params;
const { body } = ctx.request;
try {
const postData = { ...body, createdAt: new Date() }
const post = await Post.findByIdAndUpdate(id, postData);
ctx.redirect(`/post/${post.id}`);
} catch(e) {
ctx.throw(e);
}
}
};
We also need to create the corresponding template files for the needed actions. Let's create a post folder in the views folder to house the post specific views needed. This will contain the following files:
- create.ejs:
<%- include( '../partials/header.ejs' ) -%>
<div class="columns">
<div class="column is-three-quarters">
<h1 class="title">Create Post</h1>
<form action="/post/" method="POST">
<div class="field">
<label class="label">Title</label>
<div class="control">
<input class="input" type="text" placeholder="e.g Koa is
awesome" name="title">
</div>
</div>
<div class="field">
<label class="label">Content</label>
<div class="control">
<textarea rows="12" class="textarea" placeholder="e.g.
Hello world" name="content"></textarea>
</div>
</div>
<div class="control">
<button class="button is-primary">Submit</button>
</div>
</form>
</div>
<div class="column">
</div>
</div>
<%- include( '../partials/footer.ejs' ) -%>
- edit.ejs:
<%- include( '../partials/header.ejs' ) -%>
<div class="columns">
<div class="column is-three-quarters">
<h1 class="title">Create Post</h1>
<form action="/post/<%= post.id %>/" method="POST">
<input type="hidden" name="method" value="PUT">
<div class="field">
<label class="label">Title</label>
<div class="control">
<input value="<%= post.title %>" class="input"
type="text" placeholder="e.g Koa is awesome"
name="title">
</div>
</div>
<div class="field">
<label class="label">Content</label>
<div class="control">
<textarea rows="12" class="textarea" placeholder="e.g.
Hello world" name="content"><%= post.content %>
</textarea>
</div>
</div>
<div class="control">
<button class="button is-primary">Submit</button>
</div>
</form>
</div>
<div class="column">
</div>
</div>
<%- include( '../partials/footer.ejs' ) -%>
- show.ejs:
<%- include( '../partials/header.ejs' ) -%>
<h2 class="title"><%= post.title %></h2>
<div class="columns">
<div class="column is-2">
<img src="<%= post.image %>" alt="<%= post.title %>">
<h5 class="subtitle">
<small>by <%= post.author.fullName %></small>
</h5>
<% if (locals.user && (post.author.id == user._id)) { -%>
<p><small><a href="/post/<%= post.id %>/edit">Edit Post</a>
</small></p>
<% } -%>
</div>
<div class="column">
<p><%= post.content %></p>
</div>
</div>
<%- include( '../partials/footer.ejs' ) -%>
We also need to update our index.ejs file, as shown, to show the blog posts:
<!-- ./views/index.ejs -->
<%- include( 'partials/header.ejs' ) -%>
<div class="columns">
<% posts.forEach(post => { %>
<div class="column is-one-third">
<div class="card">
<div class="card-content">
<p class="title">
<%= post.title %>
</p>
<p class="subtitle">
<small>by <%= post.author.fullName %></small>
</p>
</div>
<footer class="card-footer">
<p class="card-footer-item">
<span>
<a href="/post/<%= post.id %>">View Post</a>
</span>
</p>
</footer>
</div>
</div>
<% }); %>
</div>
<%- include( 'partials/footer.ejs' ) -%>
Next, let's register these actions in our router file. We will also make the index route of our application point to the index method defined previously. Update the router as shown:
// ./middleware/router.js
const KoaRouter = require('koa-router');
const authenticated = require('./authenticated');
const guest = require('./guest');
const user = require('./user');
const authController = require('../controllers/AuthController');
const postController = require('../controllers/PostController');
const router = new KoaRouter();
router.use(user());
// base routes.
// authentication not required
router
.get('/', postController.index)
.get('/post/:id', postController.show);
// auth routes
const auth = new KoaRouter()
.get('/', guest(), authController.index)
.post('/login', authController.login)
.post('/register', authController.register)
.get('/logout', authController.logout);
router.use('/auth', auth.routes());
// blog post routes
const posts = new KoaRouter();
posts
.use(authenticated())
.post('/', postController.store)
.get('/create', postController.create)
.put('/:id', postController.update)
.get('/:id/edit', postController.edit);
router.use('/post', posts.routes());
module.exports = router;
To make put requests work, we need to implement a method overriding middleware. This will help us change the request method for the post request to update a post to be converted to a put request. We do this by creating a method-override.js file and putting the following contents as shown:
// ./middleware/method-override.js
module.exports = () => {
return async (ctx, next) => {
const { method } = ctx.request.body;
if (method) ctx.method = method;
await next();
};
};
Then, we register the middleware in our index.js as given:
// ./index.js
// ...
const methodOverride = require('./middleware/method-override');
app.use(methodOverride());
// ...
And that's it! Our blog post built from scratch in Koa is complete.