Learn to Code via Tutorials on Repl.it!

← Back to all posts
Writing a blog in go!
vityavv (60)

Preface

I started this tutorial before the contest opened, but because of school and other complications I was only able to post it today. I hope you see how much effort I put into it and give it an upvote, even though I'm probably submitting too late to win.

Note

The repl at the bottom might not work, for reasons that will become clear to you if you follow the tutorial. However, don't worry, as the code in it is still functional, and if you follow the tutorial in your own repl you'll be able to make it work!

Anyway, without further adieu, here it is!

If you have trouble viewing this on repl.it, I also have it in a Github Gist

Writing a blog in go!

CODE

About this tutorial

  • Why go? - Go is a really fast and underappreciated programming language. It is surprisingly simple, especially compared to other low-level languages like rust and c++, and it's very very easy to make web servers in it
  • What is this tutorial based on? - This is based on scms, a CMS I wrote a while ago that is extremely simple
  • Will this tutorial cover all of the features in scms? - Since this is only a short, one-part tutorial, it will not cover the API, SQL database, Markdown (you can't install go packages yet, anyway), or drafts.

Before the tutorial

Before the tutorial, you should have a basic understanding of Go. Tour of Go should help you out!

Getting started - managing articles

First, let's make a file to manage our articles.

Each article will be a json file that looks like this:

So, let's make a struct in our new file, articles.go, to reflect this:

You may be wondering "Why can't we just have title string and content string? Well, json.Unmarshal (we'll talk about this function in a bit) only puts values into capitalizd struct values, so we have to make them capitalized and then use tags to tell json.Unmarshal what to put there. Anyway, before we begin making our functions, we have to get one more thing out of the way:

One option when making a blog like this is to load the json file each time you get it. However, we're smarter than that. We can store each article in memory beforewards to make the application much faster, and easier to make. As a downside, the more articles you have, the more memory you're going to use. However, articles are just text, which should not take up that much space. Another concern here is why I used map[int]Page instead of []Page. I did this because each article will have an ID as it's name... and if an article gets deleted, or if they're somehow out of order in the next function, it will be harder to compensate. Even if you decided to make a slice and just skip the deleted files, imagine this scenario: someone has three articles, 1.json, 2.json, and 3.json, but they delete 2.json. There's a bunch of links to the third article, but they all break because it gets deleted from the slice! However, if map is used, you can avoid this problem.

Finally, we can get to making our first function, and the most important of them all: the FileInit function. The FileInit function should be run at the beginning to load each article into the pages map we made earlier. Let's break it down to it's basic parts.

The first couple of lines are:

This is a new import! Make sure you have your file set up correctly, with package main at the top, and then in your imports add io/ioutil, used for reading and writing files. Now, let me explain what these lines do: they get a list of the files inside of the articles directory, or folder. The next three lines are basic error handling, which also use the log package.

For the rest of the tutorial, I will be ommitting error handling for sake of brevity. Every time you see a variable called err, assume that following that line is error handling as shown above.

After we get that list, we do this:

I've annotated the code so you can read through it, but basically it reads each file and puts it in the pages map. That's pretty much it, for the FileInit function!

Our next function will be a function called GetFrontPage. The front page of our blog will have our five most recent articles on it. Here it is, annotated:

But where did this function, getPageNumbers, come from? Well, as discussed earlier, we don't always have the page numbers as 1, 2, 3, 4, and 5. Sometimes they're out of order, and with gaps in them. So, I wrote a helper function to get me the page numbers. Have a look:

We only a couple of functions left. An imediately obvious one is the function to get a single article:

Another obvious one is to get all of them, so we can have links to them! However, this one is blatently obvious.

And finally, we come to our missing links. This function is called CreateArticle, and it creates a new article, writes it to the file, and adds it to our pages array. Here it is, annotated:

And our final function, much simpler, is to remove an article. Here it is:

And with that, we are done with our articles file!

Part two: Serving the pages

Part 2.1: The templates

Go is a wonderful programming language for so many reasons, but one of them is that it has built in HTML templates! With that in mind, I created three different templates for the three main pages that will go into our blog, using Go's html/template module. In part 2.2, I'll talk about how I use these, but before I do that.

By the way, if you're viewing these files in the GitHub Gist, they are just called blahblahblah.html, but in the repl, and the final application, all of them are in the templates folder.

First, we have the front page. As discussed previously, the front page has the five latest articles on it. To do that, I have this code:

Second, is our archive, which lets us see links to every single article. Since we use GetAllArticles() here, and that returns a map, we can use the map keys to provide links to each article.

And finally, our simplest page, the article page, which shouldn't really need annotation:

That's it for our templates!

Part 2.2: Serving

Now that we have our files, we have to serve them to the user through our webpage! We do this with the help of one very special package, net/http! In clasical low-level languages, the default http solution is usually either non-existent or very hard to use. However, with go, it is actually quite easy to use net/http, even easier than express for node.js in some cases (e.g. built-in form parsing). Anyway, it handles a lot like express, but if you don't know express, don't worry about it, because I will be going through each line of code, step by step.

The file we will be writing to is main.go. Our first function will be the simplest and most important---the main function

while this is a simple function, it packs a lot of information. Let's look at http.HandleFunc. http.HandleFunc will set a function as a handler, meaning that it will call that function when the specified url is encountered. Since we have "/articles/" set to articleFunc, every time the server gets a request for /articles/..., it will call articleFunc with its parameters. If the request doesn't start with /articles/..., it will use the next one, which in our case is /, the catch-all, and it will call httpFunc. Here's the two functions:

And here's httpFunc, the simpler one:

Now I've got you hooked---surely, you are wondering "What is this executeTemplate function? How does it work?!?"---here, we use another wonderful built-in method of Go: the built in HTML Templates! Wait... we've heard this one before, haven't we? Well here we are, putting our wonderful templates to good use. We start with this line, at the beginnning (after all of the imports, of course)

Basically, with this line, we make a templates variable and set it to all of the templates in our templates folder (ParseGlob). The template.Must part is basically just a convinient wrapper around it saying that if there's an error, the app should exit immediately with that error. Since this happens at the very start and at no other time, this is OK! Anyway, let's look at our executeTemplates function to see how we managed to pull this off:

This function takes three parameters. It needs the http.ResponseWriter from our http funcs so that it can write the response to them. It also needs to know what template is being executed. Finally, it needs the content. Since the content and template are different each time, we use an "interface{}", meaning we don't really know the type. In fact, we don't have to know the type at all, because ExecuteTemplate takes a "interface{}" for its content, so as long as we match everything up when we call the function, we should be fine.

That's pretty much it for our main.go file... so far...

Part 3 - Administration!

This is our final and hardest part, and that is being able to delete and create articles without booting into the repl. Since you can't get packages for go yet, you have to do some work arounds, which I spent a lot of time finding out, so you're going to have to carefully follow these steps:

  • Make yourself an explorer (how)
  • Open the command pallete by making sure the editor is in focus and pressing F1
  • Type in shell, press enter
  • Type in: go get golang.org/x/crypto/bcrypt
  • Press enter. You might get an error, ignore it (unless it leads to further issues)

Why are we doing all this? Well, we can't just store our password in plain text! We have to make sure that it is protected securely, and the way to do that is to use the bcrypt library (there are some others you can use too, but bcrypt is pretty much the industry standard). Other than the first step, which you only need to do once, you may have to do this every time you load up your repl, because of how repl.it works, unfortunately.

Anyway, with that out of the way, let's look at how we're going to do things.

We will have a "/dashboard" page, pretty similar to our "/archive" page, but this time we will add buttons to delete articles and to create new ones. This part is pretty simple, so we can add this case to our switch inside of httpFunc:

The dashboard itself, though, will be a little bit more complicated. It utilizes javascript to make a "DELETE" request to the server when articles are deleted, but you don't have to worry about knowing javascript, because I can walk you through it:

See? That wasn't so hard. But wait, how do we handle these requests? Well, let me introduce you to the next function in our main.go file, deleteFunc. This will also introduce us to how go's bcrypt library works. To use this function, put http.HandleFunc("/delete/", deleteFunc) in your main function, anywhere above the "/" handler.

If you try running that code in it's current state, you will notice that it errors. In fact, it will say that pwHash is not defined! So, let's fix that.

  • First, go to a website that generates bcrypt hashes, and put in the password you want to do. Here's a site that does it!
  • Then, make a .env file in your repl.
  • Inside of that file, put PASSWORD=<your bcrypt hash>, where <your bcrypt hash> is replaced with the hash that the website generated
  • Last two steps! Put var pwHash = os.Getenv("PASSWORD") at the top of your file, and...
  • Put the following code at top of your main function

Now, if you've done all of these steps correctly, you should have a working dashboard, where you can delete articles! But, there is one more thing. We need to be able to add new pages as well! Let's first make a page, called new.html, where the user can put in a new article:

Finally, we have to handle the article. However, you might notice that I made the form make a POST request to /new. Isn't the page called /new.html? When we handle this, we're going to check the request type. If it's a post request, we process it. If it's any other type of request, including a GET request, we will send the page. Here's how we handle it, the final part to our program:

And that's it! We are done with our Blog!

Next Steps

I left a challenge in the tutorial! Take a look at DeleteArticle and GetArticle in our articles.go file, and see how they differ from the other functions in that file. They both return an error as their last (or only) return value! Your goal is to reformat all of the other functions to return an error as well, instead of using log.Fatal(), which kills the blog. Finally, every time these functions are used, make it so that if there was an error, it returns an error to the client with an HTTP code 500 (Internal Server Error), like in our executeTemplate function (main.go) or our deleteFunc function (main.go).

Comments
hotnewtop
timmy_i_chen (1200)

This is pretty awesome, thanks for making it!

isakkeyten (8)

I get

JenniferPauli (0)

Wow, you did an incredible job. Everything is described in such detail, I will definitely use this when creating my own blog. I recommend reading one of my articles about quoting famous people, it is very interesting and informative.
I will follow your posts, you are a very talented person. I love it when people create something useful for others.

JaneJLocane (1)

Writing a blog or article is not an easy task. I had problems with this both at school and at the university. Essay Geeks helped me when I had to submit my essays and term paper in one deadline. Thanks to this, I was able to properly plan my time. And I didn't have to sit for days reading materials.

Elizabeththy (0)

Such a great work, thank you for all the details!

JackyApril (2)

For many students, the process of how to choose a good essay service can be a confusing one. You might even think that there is no way to make this decision. You have read many great reviews from students who have used some of the most well-known essay writing services. Yet, you have also heard from many students who are less fortunate in having such a stellar support. Therefore, it seems to me that it is better to trust my advice and give preference to https://supremedissertations.com/ because it has good reviews.