Skip to main content

Echo GORM Setup

Introduction

When building web applications with Go's Echo framework, you'll often need a reliable way to interact with databases. This is where GORM (Go Object Relational Mapper) comes in. GORM is one of the most popular ORM libraries for Go, providing a developer-friendly API for database operations.

In this tutorial, we'll walk through the process of integrating GORM with the Echo framework, setting up database connections, defining models, and performing basic CRUD (Create, Read, Update, Delete) operations.

Prerequisites

Before we begin, make sure you have:

  • Go installed on your system (version 1.16 or later recommended)
  • Basic knowledge of Go programming
  • Familiarity with the Echo framework
  • A database system installed (we'll use PostgreSQL in our examples, but GORM supports MySQL, SQLite, and others)

Installing Required Packages

Let's start by installing Echo and GORM packages:

bash
go get github.com/labstack/echo/v4
go get gorm.io/gorm
go get gorm.io/driver/postgres # For PostgreSQL

For other databases, you might need:

bash
go get gorm.io/driver/mysql     # For MySQL
go get gorm.io/driver/sqlite # For SQLite

Creating the Database Connection

First, we'll create a database connection using GORM. Let's create a separate file for our database configuration:

go
// database/database.go
package database

import (
"fmt"
"log"
"os"

"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)

var DB *gorm.DB

// Connect initializes the database connection
func Connect() {
var err error

dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable",
os.Getenv("DB_HOST"),
os.Getenv("DB_USER"),
os.Getenv("DB_PASSWORD"),
os.Getenv("DB_NAME"),
os.Getenv("DB_PORT"),
)

DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})

if err != nil {
log.Fatal("Failed to connect to database: ", err)
}

fmt.Println("Database connection established")
}

Defining a Model

Now, let's create a simple model to work with:

go
// models/user.go
package models

import (
"gorm.io/gorm"
)

type User struct {
gorm.Model
Name string `json:"name"`
Email string `json:"email" gorm:"uniqueIndex"`
Password string `json:"password"`
}

The gorm.Model embeds common fields: ID, CreatedAt, UpdatedAt, and DeletedAt for soft deletion.

Auto Migrating the Schema

With GORM, we can automatically create or update the database schema based on our models:

go
// database/database.go (append to the Connect function)

func Connect() {
// ... existing code ...

// Auto migrate the schema
err = DB.AutoMigrate(&models.User{})
if err != nil {
log.Fatal("Failed to migrate database: ", err)
}
}

Integrating GORM with Echo

Now, let's connect everything with Echo. First, let's create our main application file:

go
// main.go
package main

import (
"net/http"
"your-app/database"
"your-app/models"

"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)

func main() {
// Initialize database
database.Connect()

// Create Echo instance
e := echo.New()

// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())

// Routes
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
})

// User routes
e.POST("/users", createUser)
e.GET("/users", getUsers)
e.GET("/users/:id", getUser)
e.PUT("/users/:id", updateUser)
e.DELETE("/users/:id", deleteUser)

// Start server
e.Logger.Fatal(e.Start(":8080"))
}

Implementing CRUD Operations

Let's implement the handlers for our User routes:

go
// handlers/user.go
package handlers

import (
"net/http"
"strconv"
"your-app/database"
"your-app/models"

"github.com/labstack/echo/v4"
)

// CreateUser creates a new user
func CreateUser(c echo.Context) error {
user := new(models.User)
if err := c.Bind(user); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{
"error": "Invalid request payload",
})
}

result := database.DB.Create(user)
if result.Error != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{
"error": result.Error.Error(),
})
}

return c.JSON(http.StatusCreated, user)
}

// GetUsers fetches all users
func GetUsers(c echo.Context) error {
var users []models.User
result := database.DB.Find(&users)
if result.Error != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{
"error": result.Error.Error(),
})
}

return c.JSON(http.StatusOK, users)
}

// GetUser fetches a single user by ID
func GetUser(c echo.Context) error {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{
"error": "Invalid ID",
})
}

var user models.User
result := database.DB.First(&user, id)
if result.Error != nil {
return c.JSON(http.StatusNotFound, map[string]string{
"error": "User not found",
})
}

return c.JSON(http.StatusOK, user)
}

// UpdateUser updates a user by ID
func UpdateUser(c echo.Context) error {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{
"error": "Invalid ID",
})
}

// First check if user exists
var existingUser models.User
if err := database.DB.First(&existingUser, id).Error; err != nil {
return c.JSON(http.StatusNotFound, map[string]string{
"error": "User not found",
})
}

// Bind new data
updatedUser := new(models.User)
if err := c.Bind(updatedUser); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{
"error": "Invalid request payload",
})
}

// Update user
database.DB.Model(&existingUser).Updates(updatedUser)

return c.JSON(http.StatusOK, existingUser)
}

// DeleteUser deletes a user by ID
func DeleteUser(c echo.Context) error {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{
"error": "Invalid ID",
})
}

result := database.DB.Delete(&models.User{}, id)
if result.Error != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{
"error": result.Error.Error(),
})
}

if result.RowsAffected == 0 {
return c.JSON(http.StatusNotFound, map[string]string{
"error": "User not found",
})
}

return c.JSON(http.StatusOK, map[string]string{
"message": "User deleted successfully",
})
}

Now, update the main file to import and use these handlers:

go
// main.go (updated)
package main

import (
"your-app/database"
"your-app/handlers"

"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)

func main() {
// Initialize database
database.Connect()

// Create Echo instance
e := echo.New()

// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())

// Routes
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
})

// User routes
e.POST("/users", handlers.CreateUser)
e.GET("/users", handlers.GetUsers)
e.GET("/users/:id", handlers.GetUser)
e.PUT("/users/:id", handlers.UpdateUser)
e.DELETE("/users/:id", handlers.DeleteUser)

// Start server
e.Logger.Fatal(e.Start(":8080"))
}

Setting Environment Variables

Let's create a .env file for our database configuration:

DB_HOST=localhost
DB_USER=postgres
DB_PASSWORD=password
DB_NAME=echogorm
DB_PORT=5432

And update our database connection to use a package like godotenv to load these variables:

go
// database/database.go (updated)
package database

import (
"fmt"
"log"

"github.com/joho/godotenv"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)

var DB *gorm.DB

// Connect initializes the database connection
func Connect() {
// Load environment variables
err := godotenv.Load()
if err != nil {
log.Println("Warning: .env file not found")
}

dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable",
os.Getenv("DB_HOST"),
os.Getenv("DB_USER"),
os.Getenv("DB_PASSWORD"),
os.Getenv("DB_NAME"),
os.Getenv("DB_PORT"),
)

// Rest of the code remains the same
// ...
}

Don't forget to install the godotenv package:

bash
go get github.com/joho/godotenv

Testing the API

Now that we've set up our Echo application with GORM, let's test it. Here are some curl commands to interact with our API:

  1. Create a user:
bash
curl -X POST http://localhost:8080/users \
-H "Content-Type: application/json" \
-d '{"name":"John Doe","email":"[email protected]","password":"secret123"}'
  1. Get all users:
bash
curl -X GET http://localhost:8080/users
  1. Get a specific user:
bash
curl -X GET http://localhost:8080/users/1
  1. Update a user:
bash
curl -X PUT http://localhost:8080/users/1 \
-H "Content-Type: application/json" \
-d '{"name":"John Updated","email":"[email protected]","password":"newpassword"}'
  1. Delete a user:
bash
curl -X DELETE http://localhost:8080/users/1

Advanced GORM Usage with Echo

1. Adding Custom Middleware for DB Transactions

We can create middleware to handle database transactions:

go
// middleware/transaction.go
package middleware

import (
"your-app/database"

"github.com/labstack/echo/v4"
"gorm.io/gorm"
)

func DBTransaction(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
tx := database.DB.Begin()
c.Set("tx", tx)

defer func() {
if r := recover(); r != nil {
tx.Rollback()
panic(r)
}
}()

if err := next(c); err != nil {
tx.Rollback()
return err
}

return tx.Commit().Error
}
}

Usage in routes:

go
e.POST("/users", handlers.CreateUser, middleware.DBTransaction)

2. Adding Query Scopes

GORM allows for reusable query parts through scopes:

go
// models/user.go (updated)
package models

import (
"gorm.io/gorm"
)

type User struct {
gorm.Model
Name string `json:"name"`
Email string `json:"email" gorm:"uniqueIndex"`
Password string `json:"password"`
Active bool `json:"active" gorm:"default:true"`
}

// ActiveUsers is a scope for active users only
func ActiveUsers(db *gorm.DB) *gorm.DB {
return db.Where("active = ?", true)
}

Usage in handlers:

go
// Get active users only
database.DB.Scopes(models.ActiveUsers).Find(&users)

Summary

In this tutorial, we've learned how to integrate GORM with the Echo framework to create a robust backend API. We've covered:

  1. Setting up the database connection with GORM
  2. Defining models and auto-migrating schemas
  3. Implementing CRUD operations with Echo handlers
  4. Using environment variables for configuration
  5. Testing the API with curl commands
  6. Advanced GORM features like transactions and scopes

By following these steps, you now have a solid foundation for building database-driven web applications using Echo and GORM. You can extend this setup by adding more complex models, relationships, and query operations based on your application's needs.

Further Resources

Exercises

  1. Add another model (e.g., Post) with a relationship to the User model
  2. Implement pagination for the GetUsers endpoint
  3. Add search functionality to filter users by name or email
  4. Create endpoints to handle the relationship between Users and Posts
  5. Implement proper error handling and input validation


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)