First, create a directory and then execute the following commands in that directory:
1 2 3 4 5
go mod init quick-start # Run before each execution to ensure correct project dependencies go mod tidy # Run the project go run main.go
Directory Structure
A typical web application follows the MVC structure, where M stands for Model, V for View, and C for Controller.
Model: Manages the core data and business logic of the application. It interacts with databases or other data sources and handles CRUD operations. It notifies the View when data changes.
View: Responsible for displaying the user interface, usually rendering based on Model data. Users can interact with the application through the View. The View listens for changes in the Model and refreshes automatically.
Controller: Handles user inputs (e.g., button clicks, form submissions) and coordinates interactions between Model and View. It calls Model methods or updates View states based on user actions. The Controller acts as a bridge between Model and View.
Thus, the general directory structure includes: models/controllers/middleware Where models contain model files, controllers contain controller files, and middleware contains middleware files. The views are typically developed separately as a front-end project.
// initializers/loadEnv.go /* * @Author Malred * @Date 2025-06-15 21:24:25 * @Description Connect to the database and provide a DB instance externally */ package initializers
funcConnectToMysql() { var err error dsn := os.Getenv("MYSQL_DB_URL") DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { log.Fatal("Failed to connect to database") } }
funcConnectToSqlite() { var err error dbPath := os.Getenv("SQLITE_MDB_URL") // Specify SQLite database file path via environment variable DB, err = gorm.Open(sqlite.Open(dbPath), &gorm.Config{}) if err != nil { log.Fatal("Failed to connect to database") } }
Configurations such as database settings are stored in a .env file.
1 2 3 4 5 6 7 8
# Web server port PORT=4000 # Randomly generated UUID for JWT generation SECRET=8d6f7b7d-2f24-4867-ae90-feb4fc646693 # MYSQL database connection URL MYSQL_DB_URL=root:123456@tcp(127.0.0.1:3306)/quick-starter?charset=utf8mb4&parseTime=True&loc=Local # SQLITE database connection URL SQLITE_MDB_URL=./dev.db
// Salt and hash the password hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), 10) if err != nil { t.Errorf("Failed to hash password") } fmt.Println(hash) fmt.Println(string(hash)) }
Create JWT Token
JWT (JSON Web Token) is an open standard used for securely transmitting information between parties in a network application environment. Creating a JWT token is to achieve user authentication.
// Salt and hash the password hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), 10) if err != nil { t.Errorf("Failed to hash password") } fmt.Println(hash) fmt.Println(string(hash))
// Pass in the secret key, encrypt it tokenStr, err := token.SignedString([]byte(os.Getenv("SECRET"))) if err != nil { t.Errorf("Failed to generate token") } fmt.Println(tokenStr) }
Conduct Tests
Post Information Table
Create, Read, Update, Delete (CRUD)
Implement basic operations on post information: create, delete, update, and query.
users, err := GetAllPosts() // This should be `posts` instead of `users` to match the function name and context. if err != nil || len(users) < 2 { // This should be `posts` instead of `users` to match the function name and context. t.Errorf("Failed to get all users: %v", err) // This should be `posts` instead of `users` to match the function name and context. } }
// Update post post := &models.Post{ Title: "test", Body: "test body", }
err := UpdatePost(post) if err != nil { t.Errorf("Failed to update user: %v", err) // This should be `post` instead of `user` to match the function name and context. } }
// Delete post err := DeletePost(uint(ID)) // This should be `post` instead of `user` to match the function name and context. if err != nil { t.Errorf("Failed to delete user: %v", err) // This should be `post` instead of `user` to match the function name and context. } }
var user models.User initializers.DB. Where("email = ?", body.Email). First(&user)
// Already exists (actually, username is also unique, designed in the table) if user != (models.User{}) { c.JSON(500, gin.H{ "error": "the email is already exist!", }) return }
// Hash the password hash, err := bcrypt.GenerateFromPassword([]byte(body.Password), 10) if err != nil { c.JSON(500, gin.H{ "error": "failed to hash password", }) return }
user = models.User{ Username: body.Username, Password: string(hash), Email: body.Email, }
result := initializers.DB. Create(&user)
if result.Error != nil { c.JSON(500, gin.H{ "error": "failed to create user", }) return }
c.JSON(200, gin.H{ "user": user, }) }
// Login funcLogin(c *gin.Context) { var body struct { Username string`json:"username"` Password string`json:"password"` }
if err := c.Bind(&body); err != nil { c.JSON(400, gin.H{ "error": "failed to bind the body!", }) return }
var user models.User initializers.DB. Where("username = ?", body.Username). Find(&user)
if user == (models.User{}) { c.JSON(500, gin.H{ "error": "con't find the user!", }) return }
// Verify password err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(body.Password)) if err != nil { c.JSON(400, gin.H{ "error": "failed to hash password", }) return } // Generate JWT token (use HTTPS for transmission protocol, encrypted transmission, prevent JWT leakage) token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "sub": user.ID, // Expiration time "exp": time.Now().Add(time.Hour * 24 * 30).Unix(), }) // Pass in the secret key, encrypt it tokenStr, err := token.SignedString([]byte(os.Getenv("SECRET"))) if err != nil { c.JSON(400, gin.H{ "error": "Failed to create token", }) return } // Set cookie c.SetSameSite(http.SameSiteLaxMode) c.SetCookie("Authorization", tokenStr, 3600*24*30, "", "", false, true) c.JSON(200, gin.H{ "token": tokenStr, "userId": user.ID, }) }
// Add Post funcPostsCreate(c *gin.Context) { // Get JSON data var body struct { Body string`json:"body" validate:"min=6,max=255"` Title string`json:"title" validate:"min=3,max=20"` } // Bind JSON data to the struct c.Bind(&body)
var posts []models.Post initializers.DB. Scopes(models.Paginate(curPage, limit)). Find(&posts)
c.JSON(200, gin.H{ "posts": posts, }) }
// Get Post by ID funcPostsShow(c *gin.Context) { id := c.Param("id")
var post models.Post initializers.DB.First(&post, id)
c.JSON(200, gin.H{ "post": post, }) }
// Update Post by ID funcPostsUpdate(c *gin.Context) { id := c.Param("id")
var body struct { Body string`json:"body" validate:"min=6,max=255"` Title string`json:"title" validate:"min=3,max=20"` } // Bind JSON data to the struct c.Bind(&body)
// Delete Post by ID funcPostsDelete(c *gin.Context) { id := c.Param("id")
initializers.DB.Delete(&models.Post{}, id)
c.Status(200) }
Authentication and CORS Middleware
The authentication middleware is used to verify user identity, ensuring that only authorized users can access specific resources. The CORS middleware is used to resolve cross-origin request issues, allowing requests from different domains to access server resources.