Table of Contents
- Introduction
- Prerequisites
- Setting Up the Project
- Creating the Database Schema
- Implementing CRUD Operations
- Building the User Interface
- Conclusion
Introduction
In this tutorial, we will learn how to build a Content Management System (CMS) using Go. A CMS is a web application that allows users to create, manage, and publish content on the internet. By the end of this tutorial, you will have a functional CMS that can perform basic CRUD (Create, Read, Update, Delete) operations on content.
Prerequisites
To follow along with this tutorial, you should have a basic understanding of the Go programming language. Familiarity with web development concepts like HTML and CSS will also be helpful. Additionally, make sure you have Go installed on your machine.
Setting Up the Project
First, let’s set up our project structure and initialize a Go module. Open your terminal and create a new directory for your project:
$ mkdir cms
$ cd cms
Next, initialize the Go module:
$ go mod init github.com/your-username/cms
This will create a go.mod
file that will track the dependencies for our project.
Creating the Database Schema
Our CMS will require a database to store the content. We will be using SQLite as our database engine. Install the necessary SQLite driver by running the following command:
$ go get github.com/mattn/go-sqlite3
Now, let’s create a file called database.go
and define the database schema:
package main
import (
"database/sql"
"fmt"
_ "github.com/mattn/go-sqlite3"
)
func main() {
db, err := sql.Open("sqlite3", "./cms.db")
if err != nil {
fmt.Println(err)
return
}
defer db.Close()
// Create the content table
_, err = db.Exec(`
CREATE TABLE IF NOT EXISTS content (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT,
body TEXT
)`)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Database schema created successfully.")
}
In this code, we opened a connection to the SQLite database using the sql.Open
function. Then, we created the content
table with an id
column, title
column, and body
column. Finally, we printed a success message to indicate that the schema creation was successful.
To run this code and create the database schema, execute the following command:
$ go run database.go
Implementing CRUD Operations
Now that we have the database schema set up, let’s implement the CRUD operations for our CMS. Create a new file called crud.go
and add the following code:
package main
import (
"database/sql"
"fmt"
_ "github.com/mattn/go-sqlite3"
)
type Content struct {
ID int
Title string
Body string
}
func createContent(db *sql.DB, content Content) error {
_, err := db.Exec("INSERT INTO content (title, body) VALUES (?, ?)", content.Title, content.Body)
return err
}
func getContent(db *sql.DB, id int) (Content, error) {
var content Content
row := db.QueryRow("SELECT * FROM content WHERE id = ?", id)
err := row.Scan(&content.ID, &content.Title, &content.Body)
if err != nil {
return content, err
}
return content, nil
}
func updateContent(db *sql.DB, id int, newContent Content) error {
_, err := db.Exec("UPDATE content SET title = ?, body = ? WHERE id = ?", newContent.Title, newContent.Body, id)
return err
}
func deleteContent(db *sql.DB, id int) error {
_, err := db.Exec("DELETE FROM content WHERE id = ?", id)
return err
}
func main() {
db, err := sql.Open("sqlite3", "./cms.db")
if err != nil {
fmt.Println(err)
return
}
defer db.Close()
// Test the CRUD operations
err = createContent(db, Content{Title: "First Post", Body: "This is the first post"})
if err != nil {
fmt.Println(err)
return
}
content, err := getContent(db, 1)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Retrieved content:", content)
err = updateContent(db, 1, Content{Title: "Updated Post", Body: "This post has been updated"})
if err != nil {
fmt.Println(err)
return
}
content, err = getContent(db, 1)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Updated content:", content)
err = deleteContent(db, 1)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Content deleted successfully.")
}
In this code, we defined the Content
struct to represent the content stored in the database. Then, we implemented four functions: createContent
to create new content, getContent
to fetch content by its ID, updateContent
to update existing content, and deleteContent
to remove content from the database.
In the main
function, we tested these CRUD operations by creating a new content, retrieving it, updating it, and deleting it.
To run this code, execute the following command:
$ go run crud.go
Building the User Interface
Now that we have the backend functionality in place, let’s build a simple user interface to interact with our CMS. Create a new file called main.go
and add the following code:
package main
import (
"database/sql"
"fmt"
"html/template"
"log"
"net/http"
"strconv"
_ "github.com/mattn/go-sqlite3"
)
type Content struct {
ID int
Title string
Body string
}
func createContent(db *sql.DB, content Content) error {
_, err := db.Exec("INSERT INTO content (title, body) VALUES (?, ?)", content.Title, content.Body)
return err
}
func getContent(db *sql.DB, id int) (Content, error) {
var content Content
row := db.QueryRow("SELECT * FROM content WHERE id = ?", id)
err := row.Scan(&content.ID, &content.Title, &content.Body)
if err != nil {
return content, err
}
return content, nil
}
func updateContent(db *sql.DB, id int, newContent Content) error {
_, err := db.Exec("UPDATE content SET title = ?, body = ? WHERE id = ?", newContent.Title, newContent.Body, id)
return err
}
func deleteContent(db *sql.DB, id int) error {
_, err := db.Exec("DELETE FROM content WHERE id = ?", id)
return err
}
func mainHandler(w http.ResponseWriter, r *http.Request) {
db, err := sql.Open("sqlite3", "./cms.db")
if err != nil {
log.Println(err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
defer db.Close()
if r.Method == "GET" {
id, err := strconv.Atoi(r.URL.Query().Get("id"))
if err != nil {
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
content, err := getContent(db, id)
if err != nil {
log.Println(err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
tmpl := template.Must(template.ParseFiles("template.html"))
err = tmpl.Execute(w, content)
if err != nil {
log.Println(err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
}
if r.Method == "POST" {
title := r.FormValue("title")
body := r.FormValue("body")
content := Content{Title: title, Body: body}
err := createContent(db, content)
if err != nil {
log.Println(err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/", http.StatusSeeOther)
}
}
func deleteHandler(w http.ResponseWriter, r *http.Request) {
db, err := sql.Open("sqlite3", "./cms.db")
if err != nil {
log.Println(err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
defer db.Close()
if r.Method == "POST" {
idStr := r.FormValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
err = deleteContent(db, id)
if err != nil {
log.Println(err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/", http.StatusSeeOther)
}
}
func main() {
http.HandleFunc("/", mainHandler)
http.HandleFunc("/delete", deleteHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
In this code, we defined two HTTP request handlers: mainHandler
and deleteHandler
. The mainHandler
handles both GET and POST requests on the root path (“/”). For GET requests, it retrieves the content by its ID, renders an HTML template (template.html
), and sends it as the response. For POST requests, it extracts the form values for title and body, creates a new content, and redirects the user back to the root path.
The deleteHandler
handles POST requests on the “/delete” path. It extracts the ID of the content to be deleted from the form, deletes the content from the database, and redirects the user back to the root path.
To complete the user interface, create a file called template.html
and add the following HTML code:
<!DOCTYPE html>
<html>
<head>
<title>CMS</title>
</head>
<body>
<h1>CMS</h1>
{{ if .ID }}
<h2>{{ .Title }}</h2>
<p>{{ .Body }}</p>
<form action="/delete" method="post">
<input type="hidden" name="id" value="{{ .ID }}">
<button type="submit">Delete</button>
</form>
{{ else }}
<form action="/" method="post">
<label for="title">Title</label>
<input type="text" name="title" required><br>
<label for="body">Body</label>
<textarea name="body" required></textarea><br>
<button type="submit">Create</button>
</form>
{{ end }}
</body>
</html>
This HTML template conditionally renders the content if it exists, displaying the title, body, and a delete button. If there is no content, it displays a form to create new content.
To run the CMS, execute the following command:
$ go run main.go
Open your web browser and visit http://localhost:8080
to start using the CMS.
Conclusion
In this tutorial, you have learned how to build a simple Content Management System (CMS) using Go. We covered the basic steps of setting up the project, creating the database schema, implementing CRUD operations, and building a user interface. You can now extend this CMS by adding features like user authentication, pagination, and search functionality.
Remember to handle errors properly and perform input validation to ensure the security and stability of your CMS. Happy coding!