Chia sẻ
//Tìm hiểu cơ bản về Golang cho người mới bắt đầu

Tìm hiểu cơ bản về Golang cho người mới bắt đầu

Đầu tiên chúng ta tìm hiểu cơ bản

Golang, thường được gọi ngắn gọn là Go, là một ngôn ngữ lập trình mã nguồn mở được phát triển tại Google với sự tham gia của ba kỹ sư nổi tiếng: Robert Griesemer, Rob Pike và Ken Thompson. Go có cú pháp gọn gàng, rõ ràng, được thiết kế theo hướng đơn giản nhưng vẫn mang lại hiệu năng cao nhờ khả năng biên dịch trực tiếp thành binary.

Nhờ sự cân bằng giữa hiệu năng, tính đơn giảnkhả năng mở rộng, Go ngày càng được sử dụng trong Backend, API, Cloud Computing và Microservice. Đây cũng là một lựa chọn đáng tìm hiểu đối với những developer muốn xây dựng các hệ thống có hiệu năng tốt nhưng vẫn dễ đọc, dễ phát triển và bảo trì.

Thay vì chỉ tìm hiểu Go qua lý thuyết, trong bài viết này chúng ta sẽ trực tiếp xây dựng một ứng dụng User đơn giản sử dụng Go và PostgreSQL. Qua từng bước, chúng ta sẽ làm quen với cách tổ chức project, kết nối Database, xử lý HTTP request và render dữ liệu lên giao diện Web.

Xây dựng hệ thống Golang cơ bản

1. Chuẩn bị môi trường:

Trước tiên, chúng ta cần cài đặt Go trên máy tính. Có thể tải phiên bản Go phù hợp với hệ điều hành đang sử dụng từ trang chính thức:

https://go.dev/dl/

Sau khi cài đặt hoàn tất, hãy mở Terminal hoặc Command Prompt và kiểm tra phiên bản Go bằng câu lệnh dưới đây:

$
$ go version
  go version go1.25.5 windows/amd64
$ 

Nếu Terminal trả về thông tin phiên bản như trên, điều đó có nghĩa là Go đã được cài đặt thành công và có thể sử dụng từ command line.

Tiếp theo, chúng ta tạo một project đơn giản có tên user-app. Trong Go, việc quản lý project thường được thực hiện thông qua Go Modules. Go Modules giúp quản lý dependency và xác định module mà project đang sử dụng.

Chạy những câu lệnh đơn giản như sau:

$
$ mkdir user-app
$ cd user-app
$ go mod init user-app
$ go get github.com/lib/pq
$

Trong đó, go mod init user-app tạo file go.mod và xác định tên module của project. Còn go get github.com/lib/pq dùng để thêm PostgreSQL driver mà chúng ta sẽ sử dụng trong phần kết nối Database.

Ngay từ bước đầu tiên này, chúng ta đã có thể thấy một đặc điểm khá thú vị của Go: việc khởi tạo project và quản lý dependency tương đối đơn giản, không cần một cấu hình quá phức tạp.

2. Cấu trúc project:

Sau khi tạo project, chúng ta sẽ chia source code thành một số thư mục riêng biệt. Việc phân chia này không bắt buộc đối với một ứng dụng Go rất nhỏ, nhưng nó giúp code rõ ràng hơn và tạo nền tảng thuận lợi cho việc mở rộng project sau này.

Tạo những file và thư mục như bên dưới:

$
$ cd user-app
$ mkdir db models handlers templates
$ touch main.go \
  db/postgres.go \
  models/user.go \
  handlers/user\_handler.go \
  templates/users.html \
  templates/user\_detail.html
$

Chạy xong là bạn có đúng cấu trúc:

    user-app/
    ├── main.go
    ├── db/postgres.go
    ├── models/user.go
    ├── handlers/user\_handler.go
    └── templates/
        ├── users.html
        └── user\_detail.html

Mỗi thư mục trong project đảm nhận một vai trò tương đối rõ ràng. db chứa phần xử lý kết nối Database, models chứa các struct đại diện cho dữ liệu, handlers xử lý HTTP request và response, trong khi templates chứa các file HTML được render từ Backend.

Còn main.go đóng vai trò là entry point của ứng dụng. Khi chạy project, chương trình sẽ bắt đầu thực thi từ package main và hàm main().

Đây là một cấu trúc nhỏ nhưng có ý nghĩa quan trọng: thay vì đặt toàn bộ logic vào một file duy nhất, chúng ta tách các trách nhiệm khác nhau thành từng khu vực. Khi project phát triển lớn hơn, cách tổ chức này sẽ giúp developer dễ tìm code và giảm tình trạng một file trở thành “bãi chiến trường” vài nghìn dòng.

3. Thêm Database PostgreSQL:

Sau khi chuẩn bị project, bước tiếp theo là tạo Database để lưu thông tin User.

Trong ví dụ này, chúng ta tạo Database có tên testdb và một table có tên users. Table chỉ chứa một số field cơ bản là id, name, email. Đây là cấu trúc tối giản để tập trung vào cách Go giao tiếp với PostgreSQL.

Tạo table:

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name TEXT,
    email TEXT
);

Ở đây, id được sử dụng làm Primary Key. PostgreSQL sẽ tự động sinh giá trị tăng dần cho trường này nhờ kiểu SERIAL.

Trong một hệ thống thực tế, bảng User chắc chắn sẽ có nhiều thông tin hơn như trạng thái tài khoản, thời gian tạo, thời gian cập nhật, password hash hoặc các thông tin liên quan khác. Tuy nhiên, chúng ta cố tình giữ Database đơn giản để dễ quan sát luồng dữ liệu từ Database → Go → HTML.

Tiếp theo, thêm một vài User để có dữ liệu hiển thị:

INSERT INTO users (name, email)
VALUES
    ('Nguyen Van A', 'a\@gmail.com'),
    ('Tran Van B', 'b\@gmail.com');

4. Kết nối PostgreSQL:

Bây giờ chúng ta bắt đầu kết nối Go với PostgreSQL.

File:

db/postgres.go

package db // Khai báo package db, thường dùng cho các xử lý liên quan database

import (
    "database/sql"        // Thư viện chuẩn của Go để làm việc với SQL
    _ "github.com/lib/pq" // Import driver PostgreSQL
                          // Dấu _ để chỉ chạy init(), không dùng trực tiếp
)

// Biến DB dùng chung cho toàn bộ ứng dụng
var DB *sql.DB

// Connect dùng để khởi tạo kết nối tới database
func Connect() error {
    // Chuỗi kết nối (Data Source Name)
    // host: địa chỉ DB
    // port: cổng PostgreSQL
    // user/password: tài khoản DB
    // dbname: tên database
    // sslmode=disable: tắt SSL (thường dùng local)
    dsn := "host=localhost port=5432 user=postgres password=root dbname=testdb sslmode=disable"

    var err error

    // Mở kết nối tới PostgreSQL (chưa kiểm tra kết nối thật)
    DB, err = sql.Open("postgres", dsn)
    if err != nil {
        // Trả lỗi nếu không mở được kết nối
        return err
    }

    // Ping để kiểm tra kết nối DB có hoạt động không
    return DB.Ping()
}

Phần code trên chịu trách nhiệm khởi tạo kết nối đến PostgreSQL. Package database/sql là thư viện chuẩn của Go dùng để làm việc với SQL Database, trong khi github.com/lib/pq đóng vai trò PostgreSQL driver.

Một điểm đáng chú ý là câu lệnh sql.Open() không đồng nghĩa với việc Go đã thực sự xác nhận Database đang hoạt động. Vì vậy, code tiếp tục gọi DB.Ping() để kiểm tra kết nối.

Đây là một chi tiết nhỏ nhưng rất đáng nhớ khi làm việc với Database trong Go: “mở database handle” và “xác nhận Database có thể kết nối được” là hai việc khác nhau.

Trong project thực tế, thông tin như username, password, host và port cũng không nên hard-code trực tiếp trong source code. Thay vào đó, chúng thường được đưa vào environment variables hoặc secret management system để tránh lộ thông tin nhạy cảm khi source code được đưa lên Git hoặc triển khai lên server.

5. Model User:

Sau khi Database đã sẵn sàng, chúng ta cần một cấu trúc trong Go để đại diện cho dữ liệu User.

File:

models/user.go

package models // Package models dùng để chứa các struct (model) ánh xạ dữ liệu

// User đại diện cho bảng users hoặc đối tượng người dùng trong hệ thống
type User struct {
    ID    int    // ID của user (thường là primary key trong database)
    Name  string // Tên người dùng
    Email string // Email của người dùng
}

Struct User đóng vai trò như một representation của User trong application. Ba field ID, NameEmail tương ứng với các column trong Database.

Đây là một trong những khái niệm quan trọng khi học Go: thay vì sử dụng một object hierarchy phức tạp, Go thường sử dụng struct để biểu diễn dữ liệu một cách trực tiếp và rõ ràng.

Struct cũng là nền tảng để sau này chúng ta mở rộng application sang JSON API. Chẳng hạn, cùng một User struct có thể được sử dụng để lấy dữ liệu từ Database và trả về cho Frontend dưới dạng JSON.

6. Handler: Danh sách user + chi tiết user:

Tiếp theo là phần xử lý HTTP request. Đây là nơi dữ liệu từ Database bắt đầu được đưa lên giao diện Web.

File:

handlers/user_handler.go

package handlers // Package handlers chứa các HTTP handler

import (
    "html/template" // Dùng để render HTML template
    "net/http"      // Xử lý HTTP request/response

    "user-app/db"     // Package kết nối database
    "user-app/models" // Package chứa struct User
)

// UserList xử lý request hiển thị danh sách user
func UserList(w http.ResponseWriter, r *http.Request) {
    // Query lấy danh sách user từ database
    rows, _ := db.DB.Query("SELECT id, name, email FROM users")
    defer rows.Close() // Đảm bảo đóng rows sau khi xử lý xong

    var users []models.User // Slice chứa danh sách user

    // Duyệt từng dòng kết quả trả về
    for rows.Next() {
        var u models.User

        // Gán dữ liệu từ DB vào struct User
        rows.Scan(&u.ID, &u.Name, &u.Email)

        // Thêm user vào slice
        users = append(users, u)
    }

    // Load file template HTML
    tmpl := template.Must(template.ParseFiles("templates/users.html"))

    // Render template và truyền dữ liệu users vào
    tmpl.Execute(w, users)
}

// UserDetail xử lý request hiển thị chi tiết 1 user
func UserDetail(w http.ResponseWriter, r *http.Request) {
    // Lấy id từ query string (?id=1)
    id := r.URL.Query().Get("id")

    var u models.User

    // Query lấy thông tin user theo id
    db.DB.QueryRow(
        "SELECT id, name, email FROM users WHERE id=$1", id,
    ).Scan(&u.ID, &u.Name, &u.Email)

    // Load template chi tiết user
    tmpl := template.Must(template.ParseFiles("templates/user_detail.html"))

    // Render template và truyền dữ liệu user vào
    tmpl.Execute(w, u)
}

Trong file này có hai handler chính.

UserList chịu trách nhiệm lấy toàn bộ User từ Database và hiển thị danh sách. Code sử dụng db.DB.Query() để thực hiện SQL query, sau đó duyệt qua từng record bằng rows.Next().

Mỗi record được đưa vào một biến models.User thông qua rows.Scan(). Sau đó, User được thêm vào slice bằng append().

Cuối cùng, danh sách User được truyền vào HTML template để render ra trình duyệt.

Ở phía còn lại, UserDetail xử lý trang chi tiết. Handler lấy id từ query string, sau đó sử dụng QueryRow() để tìm User tương ứng trong Database.

Đáng chú ý, câu SQL sử dụng $1 thay vì nối trực tiếp giá trị id vào chuỗi SQL. Đây là cách truyền parameter cho query và là hướng tiếp cận an toàn hơn so với việc xây dựng SQL bằng string concatenation.

Trong một project production, phần này vẫn cần được xử lý kỹ hơn, chẳng hạn kiểm tra lỗi từ Query(), Scan()Execute(), xử lý trường hợp User không tồn tại, validate input và trả HTTP status code phù hợp. Tuy nhiên, với mục đích học những khái niệm nền tảng của Go, đoạn code hiện tại giúp chúng ta nhìn thấy rõ luồng xử lý.

7. HTML Template:

Sau khi Handler lấy được dữ liệu, chúng ta cần một nơi để hiển thị dữ liệu đó. Go cung cấp package html/template để làm việc với HTML Template.

File:

templates/users.html

<!-- Tiêu đề trang -->
<h1>Danh sách User</h1>

<!-- Danh sách không thứ tự -->
<ul>
  <!-- Duyệt qua danh sách user được truyền từ handler -->
  {{range .}}
    <li>
      <!-- Link tới trang chi tiết user, truyền id qua query string -->
      <a href="/user?id={{.ID}}">
        <!-- Hiển thị tên và email user -->
        {{.Name}} - {{.Email}}
      </a>
    </li>
  {{end}}
</ul>

Template sử dụng cú pháp {{range .}} để duyệt qua danh sách User được truyền từ Handler. Mỗi User sẽ được hiển thị thành một item trong danh sách.

Điểm hay của cách tiếp cận này là Backend không cần tự ghép từng chuỗi HTML bằng code. Go Template đảm nhiệm phần kết hợp dữ liệu và giao diện, giúp code dễ đọc hơn.

Đặc biệt, package html/template được thiết kế dành cho việc sinh HTML và có cơ chế escaping phù hợp, giúp giảm một số rủi ro liên quan đến việc đưa dữ liệu không đáng tin cậy vào HTML.

Tiếp theo là trang chi tiết:

templates/user_detail.html

<!-- Tiêu đề trang chi tiết user -->
<h1>Chi tiết User</h1>

<!-- Hiển thị ID của user -->
<p>ID: {{.ID}}</p>

<!-- Hiển thị tên user -->
<p>Tên: {{.Name}}</p>

<!-- Hiển thị email user -->
<p>Email: {{.Email}}</p>

<!-- Link quay lại trang danh sách user -->
<a href="/">← Quay lại</a>

Template này đơn giản hơn vì chỉ cần hiển thị thông tin của một User.

Như vậy, chúng ta đã hình thành được một luồng khá hoàn chỉnh:

Browser → HTTP Handler → Database → Go Struct → HTML Template → Browser.

Đây chính là luồng xử lý cơ bản mà chúng ta sẽ tiếp tục gặp lại dưới nhiều hình thức khác nhau khi phát triển ứng dụng Web.

8. Cập nhật main.go server:

Sau khi đã có Database, Model, Handler và Template, chúng ta cần kết nối tất cả các thành phần này lại với nhau.

File:

main.go

package main // Package main là entry point của chương trình Go

import (
    "log"      // Dùng để ghi log ra console
    "net/http" // Thư viện HTTP server của Go

    "user-app/db"       // Package xử lý kết nối database
    "user-app/handlers" // Package chứa các HTTP handler
)

func main() {
    // Kết nối tới database
    err := db.Connect()
    if err != nil {
        // Dừng chương trình nếu không kết nối được DB
        log.Fatal("Không kết nối được DB:", err)
    }

    // Khai báo route cho trang danh sách user
    http.HandleFunc("/", handlers.UserList)

    // Khai báo route cho trang chi tiết user
    http.HandleFunc("/user", handlers.UserDetail)

    // Log thông báo server đang chạy
    log.Println("Server chạy tại http://localhost:8080")

    // Khởi động HTTP server tại cổng 8080
    // nil nghĩa là dùng DefaultServeMux
    http.ListenAndServe(":8080", nil)
}

Hàm main() là nơi ứng dụng bắt đầu chạy.

Đầu tiên, chương trình gọi db.Connect() để kết nối PostgreSQL. Nếu kết nối thất bại, log.Fatal() sẽ ghi lỗi và dừng chương trình.

Sau đó, chúng ta đăng ký hai route:

/ dành cho danh sách User.

/user dành cho trang chi tiết User.

Cuối cùng, http.ListenAndServe() khởi động HTTP server trên port 8080.

Điểm thú vị ở đây là Go có sẵn một HTTP server trong thư viện chuẩn. Với một ứng dụng nhỏ, chúng ta hoàn toàn có thể xây dựng Web Server mà không cần phải cài đặt một Framework Web lớn ngay từ đầu.

Điều này giúp người học hiểu được những gì thực sự xảy ra bên dưới Framework. Khi sau này sử dụng Gin, Echo hoặc các Framework khác, việc nắm được nền tảng net/http sẽ giúp chúng ta hiểu rõ hơn Framework đang giải quyết vấn đề gì.

9. Chạy project:

Đến đây, project đã gần như hoàn chỉnh. Chúng ta chỉ cần chạy ứng dụng bằng câu lệnh:

$ 
$ go run main.go
$ 

Sau khi server khởi động thành công, mở trình duyệt và truy cập:

http://localhost:8080

Nếu mọi thứ được cấu hình đúng, chúng ta sẽ nhìn thấy trang danh sách User.

Ở trang này, dữ liệu không phải là những nội dung HTML được viết cố định. Danh sách User đang được lấy từ PostgreSQL, đưa vào struct và slice trong Go, sau đó truyền vào Template để render thành HTML.

Đây chính là điểm quan trọng nhất của ví dụ: chúng ta vừa hoàn thành một vòng đời dữ liệu tương đối đầy đủ từ Database đến giao diện người dùng.

Khi click vào tên từng người, trình duyệt sẽ chuyển đến URL có dạng /user?id=…. Handler UserDetail sẽ lấy ID từ query string, tìm User tương ứng trong Database và render trang chi tiết.

Tại trang chi tiết, chúng ta có thể nhìn thấy ID, tên và email của User. Khi bấm quay lại, trình duyệt sẽ trở về trang danh sách.

Một ứng dụng nhỏ như vậy có thể trông khá đơn giản, nhưng nếu quan sát kỹ, chúng ta đã chạm vào rất nhiều khái niệm nền tảng của Web Development: HTTP request, routing, Database connection, SQL query, data structure, template rendering và server lifecycle.

Từ đây, project có thể tiếp tục được mở rộng theo rất nhiều hướng. Chẳng hạn, chúng ta có thể thêm chức năng tạo User, chỉnh sửa User, xóa User, tìm kiếm User hoặc phân trang danh sách. Sau đó có thể tiếp tục bổ sung validation, authentication, authorization, middleware, logging và error handling.

Nếu chuyển từ Server-rendered HTML sang Frontend hiện đại, phần Handler cũng có thể được thay đổi để trả về JSON và trở thành một REST API. Khi đó, cùng một nền tảng Go hiện tại có thể phát triển thành Backend cho React, Vue, mobile application hoặc một hệ thống microservice.

Tóm lại

Qua ví dụ trên, có thể thấy Go không cố gắng làm cho việc lập trình trở nên phức tạp bằng quá nhiều abstraction. Thay vào đó, Go tập trung vào sự đơn giản, rõ ràng và hiệu năng. Dù không đi theo mô hình lập trình hướng đối tượng truyền thống, Go vẫn cung cấp struct, interface, package và composition để xây dựng các hệ thống lớn một cách có tổ chức.

Điểm hấp dẫn của Go nằm ở sự cân bằng giữa hiệu năng caotrải nghiệm lập trình thân thiện. Với cú pháp gọn gàng, thư viện chuẩn mạnh và khả năng biên dịch thành binary, Go đặc biệt phù hợp với các hệ thống Backend, API và Microservice yêu cầu khả năng xử lý tốt nhưng vẫn dễ phát triển và bảo trì.

Quan trọng hơn, học Go không chỉ là ghi nhớ cú pháp mà còn là hiểu cách tổ chức code và cách dữ liệu di chuyển giữa các thành phần. Từ một ứng dụng User đơn giản, chúng ta hoàn toàn có thể tiếp tục mở rộng thành REST API, Authentication, Docker, Kubernetes hoặc Microservice.

Thay vì chỉ dựa vào việc nâng cấp phần cứng để tăng tốc hệ thống, việc tối ưu và phân bổ trách nhiệm hợp lý ngay từ mã nguồn sẽ giúp ứng dụng vận hành ổn định, dễ bảo trì và có khả năng mở rộng tốt hơn. Và đó cũng chính là một trong những lý do Go ngày càng được quan tâm trong phát triển Backend hiện đại.

Nguyễn Trọng Nghĩa
Developer

ỨNG TUYỂN







    Chế độ phúc lợi

    CHÍNH SÁCH LƯƠNG & THƯỞNG

    Thấu hiểu tâm tư nguyện vọng của nhân viên, công ty Rivercrane Việt Nam đặc biệt thiết lập chế độ xét tăng lương định kỳ 2lần/năm. Xét đánh giá vào tháng 06 và tháng 12 hàng năm và thay đổi lương vào tháng 01 và tháng 07 hàng năm. Ngoài ra, nhân viên còn được thưởng thành tích định kỳ cho các cá nhân xuất sắc trong tháng, năm.

    CHẾ ĐỘ ĐÀO TẠO TẠI NHẬT

    Luôn luôn mong muốn các kỹ sư và nhân viên trong công ty có cái nhìn toàn diện về lập trình những mảng kỹ thuật trên thế giới, công ty Rivercrane Việt Nam quyết định chế độ 3 tháng 1 lần đưa nhân viên đi học tập tại Nhật. Các bạn kỹ sư hoàn toàn đều có thể quyết định khả năng phát triển bản thân theo hướng kỹ thuật hoặc theo hướng quản lý.

    CHẾ ĐỘ ĐI DU LỊCH HÀNG NĂM

    Không chỉ đưa đến cho nhân viên những công việc thử thách thể hiện bản thân, công ty Rivercrane Việt Nam muốn nhân viên luôn thích thú khi đến với những chuyến hành trình thú vị hàng năm. Những buổi tiệc Gala Dinner sôi động cùng với những trò chơi Team Building vui nhộn sẽ giúp cho đại gia đình Rivercrane thân thiết hơn.

    CHẾ ĐỘ EVENT CÔNG TY

    Những hoạt động Team building, Company Building, Family Building, Summer Holiday, Mid-Autumn Festival… sẽ là những khoảnh khắc gắn kết đáng nhớ của mỗi một nhân viên trong từng dự án, hoặc sẽ là những điều tự hào khi giới thiệu công ty mình với với gia đình thân thương, cùng nhau chia sẻ yêu thương với thông điệp “We are One”

    BẢO HIỂM

    Công ty Rivercrane Việt Nam đảm bảo tham gia đầy đủ chế độ Bảo hiểm xã hội, bảo hiểm y tế và bảo hiểm thất nghiệp. Cam kết chặt chẽ về mọi thủ tục phát sinh công ty đều hỗ trợ và tiến hành cho nhân viên từ đầu đến cuối. Những chế độ bảo hiểm khác công ty cũng đặc biệt quan tâm và từng bước tiến hành.

    CHẾ ĐỘ PHÚC LỢI KHÁC

    Hỗ trợ kinh phí cho các hoạt động văn hóa, văn nghệ, thể thao; Hỗ trợ kinh phí cho việc mua sách nghiên cứu kỹ thuật; Hỗ trợ kinh phí thi cử bằng cấp kỹ sư, bằng cấp dành cho ngôn ngữ. Hỗ trợ kinh phí tham gia các lớp học về quản lý kỹ thuật bên ngoài; Các hỗ trợ phúc lợi khác theo quy định công ty…

    © 2012 RiverCrane Vietnam. All rights reserved.

    Close