Like we did for the Contact page, we can use an HTML form to handle user input.
Let’s add some handlebars blocks to handle all the error cases.
<!DOCTYPE html><html><head><title>Create a new post</title>{{>head}}</head><body>{{>header}}<mainclass="main blog"><divclass="container"><h1class="heading"><ahref="/blog">Blog</a><strong>Create post</strong></h1>{{#iferror}}<pclass="error">{{message}}</p>{{/if}}{{#ifsuccess}}<pclass="success">{{message}}<ahref="/blog/{{postId}}">View post</a></p>{{/if}}<formclass="form"method="post"action="/create-post"><divclass="field"><labelclass="label">Title</label><inputclass="input"type="text"name="title"value="{{formBody.title}}"></div>{{#ifmissingTitle}}<pclass="missing">The title is required.</p>{{/if}}<divclass="field"><labelclass="label">Excerpt</label><inputclass="input"type="text"name="excerpt"value="{{formBody.excerpt}}"></div><divclass="field"><labelclass="label">Content</label><textareaclass="textarea"name="content">{{formBody.content}}</textarea></div><divclass="field no-label"><buttonclass="button green">Create post</button></div></form></div></main>{{>footer}}</body></html>
As we are saving our blog posts by writing a file on the disk, we need to create two functions that will simplify that process (as we are going to use it for both creating and editing blog posts).
The first function will take a JavaScript object, and convert that into a valid JSON object. Although they look similar, there are slight differences. Luckily, we can use the JSON.stringify function.
The second function takes a Post object, and adds it to the blogPosts object, and then saves it to the blog-posts.json file.
functionsaveJSONToFile(filename,json){// Convert the blogPosts object into a string and then save it to filefs.writeFile(filename,JSON.stringify(json,null,'\t')+'\n',function(err){// If there is an error let us know// otherwise give us a success messageif(err){throwerr;}else{console.log('It\'s saved!');}});}functionsavePost(id,object,data){// Update object with new dataobject[id]=data;// Save the updated object to filesaveJSONToFile('blog-posts.json',object);}
We need to handle both GET and POST requests to the /create-post route.
Let’s first retrieve the form body, and handle the case if the title is empty. If it is, we render the same template but with an error message, and the form body that was already inserted by the user.
// Create post pageapp.get('/create-post',function(req,res){res.render('create-post');});// Handle /create-post form submissionapp.post('/create-post',function(req,res){varformBody={'title':req.body.title,'excerpt':req.body.excerpt,'content':req.body.content,};if(!formBody.title){returnres.render('create-post',{error:true,message:'The title is required!',missingTitle:true,formBody,});}});
If the title is present, we can save the post in the blog-posts.json file. Because the JSON is an object with unique keys, and as the Post IDs must be unique as well (so that we don’t override previously createds posts), we are going to use the current timestamp as our unique identifier.
// Use the timestamp as a unique identifiervartimestamp=Date.now();varpostId=timestamp;// Save new post to filesavePost(postId,blogPosts,{id:postId,title:formBody.title,excerpt:formBody.excerpt,content:formBody.content,});res.render('create-post',{success:true,message:'New post successfully created!',postId,});
Because it’s easy to make mistakes when creating a post, we also want to be able to edit them.
Let’s create an edit-post.handlebars template, which is almost identical to the create-post.handlebars one.
<!DOCTYPE html><html><head><title>Edit a post</title>{{>head}}</head><body>{{>header}}<mainclass="main blog"><divclass="container"><h1class="heading"><aclass="button blue"href="/blog/{{postId}}">View post</a><ahref="/blog">Blog</a><strong>Edit post</strong></h1>{{#iferror}}<pclass="error">{{message}}</p>{{/if}}{{#ifsuccess}}<pclass="success">{{message}}</p>{{/if}}<formclass="form"method="post"action="/edit-post/{{postId}}"><divclass="field"><labelclass="label">Title</label><inputclass="input"type="text"name="title"value="{{formBody.title}}"></div>{{#ifmissingTitle}}<pclass="missing">The title is required.</p>{{/if}}<divclass="field"><labelclass="label">Excerpt</label><inputclass="input"type="text"name="excerpt"value="{{formBody.excerpt}}"></div><divclass="field"><labelclass="label">Content</label><textareaclass="textarea"name="content">{{formBody.content}}</textarea></div><divclass="field no-label"><buttonclass="button green">Save changes</button></div></form></div></main>{{>footer}}</body></html>
When we edit a post, it already exists. The same way we use the :post_id in the URL to show a post, we need it to edit it, so we can retrieve it from the blog-posts.json.
We can then pass the post object to the formBody, so that it’s displayed in the template.
// Edit a blog postapp.get('/edit-post/:post_id',function(req,res){varpostId=req.params['post_id'];varpost=blogPosts[postId];if(!post){res.send('Not found');}else{res.render('edit-post',{formBody:post,postId,});}});
When we submit the HTML form, we need to check again if the title is empty. The logic is similar to the /create-post route.
If the title is present, we can then use the form body to save the new edited post.
// Handle editing a blog postapp.post('/edit-post/:post_id',function(req,res){varpostId=req.params['post_id'];varpost=blogPosts[postId];varformBody={'title':req.body.title,'excerpt':req.body.excerpt,'content':req.body.content,};if(!formBody.title){returnres.render('edit-post',{error:true,message:'The title is required!',missingTitle:true,postId,formBody,});}varnewPost={id:postId,title:formBody.title,excerpt:formBody.excerpt,content:formBody.content,};// Save new post to filesavePost(postId,blogPosts,newPost);res.render('edit-post',{success:true,message:'Post successfully saved!',postId,formBody:newPost,});});
There are many ways to delete data. We are going to implement a two-step process, in order to avoid user retribution.
First, we need a template to ask for confirmation to delete a post. The template will only show the post’s title and two buttons.
<!DOCTYPE html><html><head><title>Delete a post</title>{{>head}}</head><body>{{>header}}<mainclass="main blog"><divclass="container"><h1class="heading"><aclass="button blue"href="/blog/{{postId}}">View post</a><ahref="/blog">Blog</a><strong>Delete post</strong></h1><formclass="form"method="post"action="/delete-post/{{postId}}"><pclass="confirm">
Are you sure you want to delete the post <strong>{{postTitle}}</strong>?
</p><p><buttonclass="button red">Delete post</button><aclass="button"href="/blog/{{postId}}">Cancel</a></p></form></div></main>{{>footer}}</body></html>
The same way we retrieve a post to edit it, we retrieve it before deleting it thanks to the :post_id parameter.
In the template, we only need the postId and the postTitle.
// We use this route when we want to delete a postapp.get('/delete-post/:post_id',function(req,res){varpostId=req.params['post_id'];varpost=blogPosts[postId];if(!post){res.send('Not found');}else{res.render('delete-post',{postId,postTitle:post.title,});}});
If the user clicks on “Delete post”, it submits a POST request to /delete-postwith the post_id.
With this id, we know which post to delete from the blogPosts object.
We can then use this alteredblogPosts object and save it again.
As this /delete-post/:post_id doesn’t exist anymore, let’s redirect to /blog with a notification.
// We use this route when we want to delete a postapp.post('/delete-post/:post_id',function(req,res){varpostId=req.params['post_id'];// Delete the post from the blogPosts objectdeleteblogPosts[postId];// Save the updated object to filesaveJSONToFile('blog-posts.json',blogPosts);// Tell the browser we're doneres.redirect('/blog?delete=true');});