1. Project Structure
Organize your code in a way that makes it easy to understand and extend. A common structure includes separating your code into folders such as models, handlers, routes, middlewares, utils, and config.
Example structure:
go-rest-api/
|-- main.go
|-- config/
| |-- config.go
|-- handlers/
| |-- user.go
|-- models/
| |-- user.go
|-- routes/
| |-- routes.go
|-- middlewares/
| |-- logging.go
|-- utils/
| |-- helpers.go
2. Environment Configuration
Store configuration settings (like database credentials, port numbers, etc.) in environment variables or configuration files. Use a package like viper to manage configurations.
config/config.go:
package config
import (
"github.com/spf13/viper"
"log"
)
type Config struct {
Port string
DB struct {
Host string
Port string
User string
Password string
Name string
}
}
var AppConfig Config
func LoadConfig() {
viper.SetConfigName("config")
viper.AddConfigPath(".")
viper.AutomaticEnv()
if err := viper.ReadInConfig(); err != nil {
log.Fatalf("Error reading config file, %s", err)
}
err := viper.Unmarshal(&AppConfig)
if err != nil {
log.Fatalf("Unable to decode into struct, %v", err)
}
}
3. Error Handling
Always handle errors appropriately. Return meaningful error messages and HTTP status codes.
handlers/user.go:
func GetUserHandler(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
id, err := strconv.Atoi(params["id"])
if err != nil {
http.Error(w, "Invalid user ID", http.StatusBadRequest)
return
}
user, err := findUserByID(id)
if err != nil {
http.Error(w, "User not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
4. Middlewares
Use middlewares for logging, authentication, and other cross-cutting concerns.
middlewares/logging.go:
package middlewares
import (
"log"
"net/http"
"time"
)
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %s", r.Method, r.RequestURI, time.Since(start))
})
}
In main.go or routes/routes.go:
r.Use(middlewares.LoggingMiddleware)
5. JSON Handling
Use proper JSON encoding and decoding. Validate incoming JSON data to ensure it meets the expected structure.
handlers/user.go:
func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
var user models.User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
http.Error(w, "Invalid input", http.StatusBadRequest)
return
}
// Validate user data...
users = append(users, user)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
6. Database Access
Use a database to store your data. Use a package like gorm for ORM or sqlx for raw SQL queries.
models/user.go:
package models
import "gorm.io/gorm"
type User struct {
gorm.Model
Name string `json:"name"`
Email string `json:"email"`
}
main.go:
package main
import (
"github.com/yourusername/go-rest-api/config"
"github.com/yourusername/go-rest-api/routes"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"log"
"net/http"
)
func main() {
config.LoadConfig()
dsn := "host=" + config.AppConfig.DB.Host +
" user=" + config.AppConfig.DB.User +
" password=" + config.AppConfig.DB.Password +
" dbname=" + config.AppConfig.DB.Name +
" port=" + config.AppConfig.DB.Port +
" sslmode=disable"
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatalf("Could not connect to the database: %v", err)
}
r := routes.NewRouter(db)
log.Println("Starting server on port", config.AppConfig.Port)
log.Fatal(http.ListenAndServe(":"+config.AppConfig.Port, r))
}
7. Logging
Use a structured logging library like logrus or zap for better logging.
middlewares/logging.go:
package middlewares
import (
"github.com/sirupsen/logrus"
"net/http"
"time"
)
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
logrus.WithFields(logrus.Fields{
"method": r.Method,
"url": r.URL.Path,
"time": time.Since(start),
}).Info("handled request")
})
}
8. Security
Ensure your API is secure by using HTTPS, validating and sanitizing inputs, and implementing proper authentication and authorization.
9. Versioning
Version your API to handle changes without breaking existing clients. This can be done by including the version in the URL, such as /api/v1/users.
10. Documentation
Document your API using tools like Swagger or Postman to provide clear usage instructions for developers.
By following these best practices, you can create a robust, maintainable, and scalable RESTful API in Go.
Top comments (0)