đĸ 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.