Skip to main content

Echo No-SQL Integration

Introduction

In modern web applications, traditional relational databases aren't always the best fit for every use case. No-SQL databases provide flexible schema design, horizontal scaling, and specialized data models that can better suit certain applications. Echo framework, being lightweight and flexible, works seamlessly with various No-SQL databases.

This guide walks you through integrating popular No-SQL databases with your Echo application, providing practical examples and best practices for beginners.

Why Use No-SQL with Echo?

Before diving into implementation, let's understand why you might choose a No-SQL database for your Echo application:

  • Schema flexibility: No predetermined structure requirement
  • Horizontal scaling: Easier to scale across multiple servers
  • Specialized data models: Graph, document, key-value, or column-oriented storage
  • High throughput: Often optimized for specific operations
  • Real-time applications: Many No-SQL databases excel at real-time data

Prerequisites

Before starting, ensure you have:

  • Go installed on your machine
  • Basic knowledge of Echo framework
  • A No-SQL database installed locally or accessible remotely

MongoDB Integration

MongoDB is one of the most popular document-oriented No-SQL databases, storing data in JSON-like documents.

Setting Up MongoDB with Echo

First, install the required packages:

go get go.mongodb.org/mongo-driver/mongo
go get github.com/labstack/echo/v4

Now, let's create a basic Echo application with MongoDB integration:

package main

import (
"context"
"log"
"net/http"
"time"

"github.com/labstack/echo/v4"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/bson"
)

// User represents a user in our application
type User struct {
ID string `json:"id" bson:"_id,omitempty"`
Name string `json:"name" bson:"name"`
Email string `json:"email" bson:"email"`
JoinedAt time.Time `json:"joined_at" bson:"joined_at"`
}

// MongoDB client instance
var client *mongo.Client

func main() {
// Initialize MongoDB
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

clientOptions := options.Client().ApplyURI("mongodb://localhost:27017")
var err error
client, err = mongo.Connect(ctx, clientOptions)
if err != nil {
log.Fatal(err)
}

// Check the connection
err = client.Ping(ctx, nil)
if err != nil {
log.Fatal(err)
}

// Initialize Echo
e := echo.New()

// Routes
e.GET("/users", getUsers)
e.POST("/users", createUser)
e.GET("/users/:id", getUser)

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

// getUsers returns all users
func getUsers(c echo.Context) error {
collection := client.Database("echodb").Collection("users")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

var users []User
cursor, err := collection.Find(ctx, bson.M{})
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}

if err = cursor.All(ctx, &users); err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}

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

// createUser adds a new user
func createUser(c echo.Context) error {
u := new(User)
if err := c.Bind(u); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
}

collection := client.Database("echodb").Collection("users")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

u.JoinedAt = time.Now()

result, err := collection.InsertOne(ctx, u)
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}

return c.JSON(http.StatusCreated, map[string]interface{}{"id": result.InsertedID})
}

// getUser returns a specific user
func getUser(c echo.Context) error {
id := c.Param("id")

collection := client.Database("echodb").Collection("users")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

var user User
err := collection.FindOne(ctx, bson.M{"_id": id}).Decode(&user)
if err != nil {
return c.JSON(http.StatusNotFound, map[string]string{"error": "User not found"})
}

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

Testing MongoDB Integration

To test this application, send requests using tools like curl or Postman:

Creating a user (POST /users):

// Input
{
"name": "John Doe",
"email": "[email protected]"
}

// Output
{
"id": "6172a7e9a072f1d123b45678"
}

Getting all users (GET /users):

// Output
[
{
"id": "6172a7e9a072f1d123b45678",
"name": "John Doe",
"email": "[email protected]",
"joined_at": "2023-10-18T15:30:45.123Z"
}
]

Redis Integration

Redis is a popular in-memory key-value database that's often used for caching, session storage, and pub/sub messaging.

Setting Up Redis with Echo

Install the required packages:

go get github.com/go-redis/redis/v8
go get github.com/labstack/echo/v4

Create an Echo application with Redis integration:

package main

import (
"context"
"net/http"
"time"

"github.com/go-redis/redis/v8"
"github.com/labstack/echo/v4"
)

var (
ctx = context.Background()
rdb *redis.Client
)

func main() {
// Initialize Redis
rdb = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password set
DB: 0, // use default DB
})

// Initialize Echo
e := echo.New()

// Routes
e.GET("/cache/:key", getFromCache)
e.POST("/cache", addToCache)

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

// Cache item structure
type CacheItem struct {
Key string `json:"key"`
Value string `json:"value"`
Expires int `json:"expires"` // TTL in seconds
}

// getFromCache retrieves value from Redis
func getFromCache(c echo.Context) error {
key := c.Param("key")

val, err := rdb.Get(ctx, key).Result()
if err == redis.Nil {
return c.JSON(http.StatusNotFound, map[string]string{
"error": "Key not found",
})
} else if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{
"error": err.Error(),
})
}

return c.String(http.StatusOK, val)
}

// addToCache stores value in Redis with optional expiration
func addToCache(c echo.Context) error {
item := new(CacheItem)
if err := c.Bind(item); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{
"error": err.Error(),
})
}

// Set expiration (if provided)
var expiration time.Duration
if item.Expires > 0 {
expiration = time.Duration(item.Expires) * time.Second
}

err := rdb.Set(ctx, item.Key, item.Value, expiration).Err()
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{
"error": err.Error(),
})
}

return c.JSON(http.StatusCreated, map[string]string{
"status": "success",
})
}

Testing Redis Integration

To test the Redis cache API:

Adding to cache (POST /cache):

// Input
{
"key": "user:profile:123",
"value": "John Doe's profile data",
"expires": 3600
}

// Output
{
"status": "success"
}

Retrieving from cache (GET /cache/user:profile:123):

// Output
John Doe's profile data

Practical Example: Session Management with Redis

Here's a real-world example of using Redis for session management in an Echo application:

package main

import (
"context"
"net/http"
"time"

"github.com/go-redis/redis/v8"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)

var (
ctx = context.Background()
rdb *redis.Client
)

// User represents login credentials
type User struct {
Username string `json:"username"`
Password string `json:"password"`
}

// SessionData holds user session information
type SessionData struct {
Username string `json:"username"`
LoginTime time.Time `json:"login_time"`
}

func main() {
// Initialize Redis
rdb = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password set
DB: 0, // use default DB
})

// Initialize Echo
e := echo.New()
e.Use(middleware.Logger())

// Routes
e.POST("/login", login)
e.GET("/profile", getProfile, sessionMiddleware)
e.POST("/logout", logout, sessionMiddleware)

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

// login authenticates user and creates a session
func login(c echo.Context) error {
u := new(User)
if err := c.Bind(u); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
}

// In a real app, validate against database
// This is just a simplified example
if u.Username == "admin" && u.Password == "password" {
// Create session
sessionToken := uuid.New().String()

// Create session data
sessionData := SessionData{
Username: u.Username,
LoginTime: time.Now(),
}

// Store in Redis with 24-hour expiration
err := rdb.HSet(ctx, "session:"+sessionToken,
"username", sessionData.Username,
"loginTime", sessionData.LoginTime.Format(time.RFC3339),
).Err()
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}

rdb.Expire(ctx, "session:"+sessionToken, 24*time.Hour)

// Set cookie
cookie := new(http.Cookie)
cookie.Name = "session_token"
cookie.Value = sessionToken
cookie.Expires = time.Now().Add(24 * time.Hour)
cookie.HttpOnly = true
c.SetCookie(cookie)

return c.JSON(http.StatusOK, map[string]string{"message": "Login successful"})
}

return c.JSON(http.StatusUnauthorized, map[string]string{"error": "Invalid credentials"})
}

// sessionMiddleware validates the session
func sessionMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
cookie, err := c.Cookie("session_token")
if err != nil {
return c.JSON(http.StatusUnauthorized, map[string]string{"error": "Not logged in"})
}

// Check if session exists
exists, err := rdb.Exists(ctx, "session:"+cookie.Value).Result()
if err != nil || exists == 0 {
return c.JSON(http.StatusUnauthorized, map[string]string{"error": "Invalid session"})
}

// Get username from session
username, err := rdb.HGet(ctx, "session:"+cookie.Value, "username").Result()
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}

// Set username in context
c.Set("username", username)
c.Set("session_token", cookie.Value)

return next(c)
}
}

// getProfile returns user profile based on session
func getProfile(c echo.Context) error {
username := c.Get("username").(string)

// In a real app, fetch profile data from database
// This is just a simplified example
return c.JSON(http.StatusOK, map[string]string{
"username": username,
"email": username + "@example.com",
"role": "user",
})
}

// logout ends the session
func logout(c echo.Context) error {
sessionToken := c.Get("session_token").(string)

// Delete session from Redis
err := rdb.Del(ctx, "session:"+sessionToken).Err()
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}

// Clear cookie
cookie := new(http.Cookie)
cookie.Name = "session_token"
cookie.Value = ""
cookie.Expires = time.Now().Add(-1 * time.Hour)
cookie.HttpOnly = true
c.SetCookie(cookie)

return c.JSON(http.StatusOK, map[string]string{"message": "Logout successful"})
}

This example demonstrates a common use case: user sessions with Redis as the session store.

Best Practices for No-SQL Integration with Echo

When integrating No-SQL databases with Echo applications, follow these best practices:

  1. Separation of concerns: Create a separate package for database operations
  2. Connection pooling: Reuse database connections instead of creating new ones for each request
  3. Context usage: Pass context with proper timeouts for database operations
  4. Error handling: Implement proper error handling and return appropriate HTTP status codes
  5. Input validation: Validate input data before storing in the database
  6. Close connections: Ensure connections are properly closed when your application shuts down
  7. Use middleware: For common database operations like authentication

Advanced Integration: Database Middleware

Creating middleware for database connections can make your code cleaner:

package middleware

import (
"context"
"time"

"github.com/labstack/echo/v4"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)

// MongoClient is a middleware that adds MongoDB client to the context
func MongoClient(mongoURI string) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

clientOptions := options.Client().ApplyURI(mongoURI)
client, err := mongo.Connect(ctx, clientOptions)
if err != nil {
return err
}

// Add client to context
c.Set("mongoClient", client)

// Continue
err = next(c)

// Close client when done
client.Disconnect(ctx)

return err
}
}
}

Then in your main application:

// Initialize Echo
e := echo.New()

// Apply MongoDB middleware
e.Use(middleware.MongoClient("mongodb://localhost:27017"))

// Now in your handlers
func getUsers(c echo.Context) error {
client := c.Get("mongoClient").(*mongo.Client)
collection := client.Database("echodb").Collection("users")
// Use the collection...
}

Summary

In this guide, we've explored how to integrate No-SQL databases with Echo applications:

  • Setting up MongoDB for document storage
  • Using Redis for caching and session management
  • Building practical examples for real-world use cases
  • Implementing best practices for database integration

No-SQL databases offer flexibility, scalability, and specialized data models that can complement Echo's lightweight design. By understanding when and how to use No-SQL databases, you can build more efficient, scalable web applications with Echo.

Additional Resources

Exercises

  1. Extend the MongoDB example to include update and delete operations
  2. Implement a rate limiter using Redis and Echo middleware
  3. Build a simple cache layer that falls back to database queries when cache misses
  4. Create a real-time notification system using Redis pub/sub with Echo's websockets
  5. Implement a document version history system using MongoDB

Good luck with your No-SQL integration projects with Echo! Remember that choosing the right database depends on your specific use case, and Echo's flexibility allows you to use the best tool for each job.

💡 Found a typo or mistake? Click "Edit this page" to suggest a correction. Your feedback is greatly appreciated!