GoLang Backend Intro

đŸ“ĸ This article was translated by gemini-2.5-flash

A simple, no-framework Go backend.

Backend development is typically interface-driven, or, as some say, CRUD engineering. This article will use Go to show how to read data from a database and return JSON.

Database

This example reads category names and IDs from a MySQL database’s categories table. Here’s the database structure:

1
2
3
4
5
6
DROP TABLE IF EXISTS categories ;
CREATE TABLE categories (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    description VARCHAR(255)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Project Structure

Here’s the project structure for this example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
project/
├── database/       // Database package
│   └── database.go // Database connection
├── handler/        // Handler package
│   └── category.go // Category-related API
├── model/          // Data model package
│   ├── category.go // Category table model
│   └── response.go // Response data model
├── router/         // Router package
│   └── router.go   // Router configuration
├── utils           // Utility package
│   └── response.go // Unified response
├── main.go         // Program entry point

Now, let’s break it down by directory.

database

This package manages database connections.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// database.go
package database

import (
    "database/sql"
    "fmt"
    "log"

    _ "github.com/go-sql-driver/mysql"
)

var DB *sql.DB

func InitDB() {
    var err error

    dsn := "username:password@tcp(address:3306)/name?charset=utf8"
    DB, err = sql.Open("mysql", dsn)
    if err != nil {
        log.Fatalf("Failed to open database: %v", err)
    }

    // Test Connect
    err = DB.Ping()
    if err != nil {
        log.Fatalf("Failed to ping database: %v", err)
    }

    fmt.Println("Successfully connected Database!")
}

handler

This package handles specific processing logic (think of it like the service layer in a three-tier architecture). Database operations could be further layered (like a Mapper), but for this simple example, we’ll keep it as is.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// category.go
package handler

import (
    "net/http"
    "project/database"
    "project/model"
    "project/utils"
)

func GetCategories(w http.ResponseWriter, r *http.Request) {
    // Select Database
    rows, err := database.DB.Query("select id,name from categories")
    if err != nil {
        http.Error(w, "Failed to query Categories", http.StatusInternalServerError)
        return
    }
    defer rows.Close()

    // analyze data
    var categories []model.Category
    for rows.Next() {
        var category model.Category
        err = rows.Scan(&category.Id, &category.Name)
        if err != nil {
            utils.JSONResponse(w, http.StatusInternalServerError, "Failed to analyze Categories", nil)
            //http.Error(w, "Failed to analyze Categories", http.StatusInternalServerError)
            return
        }
        categories = append(categories, category)
    }

    // return JSON
    //w.Header().Set("Content-Type", "application/json")
    //json.NewEncoder(w).Encode(categories)
    utils.JSONResponse(w, http.StatusOK, "", categories)
}

The commented-out code shows how to return JSON directly, without using the unified response interface.

model

Data models here usually split into three: those received from the frontend, those representing the database, and those returned to the frontend. Since this is just a simple example, I haven’t gone into that much detail.

1
2
3
4
5
6
7
8
// category.go
package model

type Category struct {
    Id          int    `json:"id"`
    Name        string `json:"name"`
    Description string `json:"description"`
}

The response data model is:

1
2
3
4
5
6
7
package model

type Response struct {
    Code int         `json:"code"`
    Msg  string      `json:"msf"`
    Data interface{} `json:"data"`
}

router

This package manages routes. It maps specific paths to their corresponding handler logic (like the controller layer in a three-tier architecture).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// router.go
package router

import (
    "github.com/gorilla/mux"
    "project/handler"
)

func InitRouter() *mux.Router {
    router := mux.NewRouter()
    router.HandleFunc("/categories", handler.GetCategories).Methods("GET")
    return router
}

utils

This package is for utilities, defining general-purpose helper functions.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// response.go
package utils

import (
    "encoding/json"
    "log"
    "net/http"
    "project/model"
)

func JSONResponse(w http.ResponseWriter, code int, message string, data interface{}) {
    w.Header().Set("Content-Type", "application/json; charset=utf-8")
    w.WriteHeader(http.StatusOK)
    err := json.NewEncoder(w).Encode(model.Response{Code: code, Msg: message, Data: data})
    if err != nil {
        log.Fatal(err)
        return
    }
}

main

The program’s entry point.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// main.go
package main

import (
    "log"
    "net/http"
    "project/database"
    "project/router"
)

func main() {
    // init Database
    database.InitDB()

    // Init Router
    r := router.InitRouter()

    // start
    log.Println("Starting server on port 8848")
    log.Fatal(http.ListenAndServe(":8848", r))
}

Run go run main.go, and the application will listen on port 8848.