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:

bash
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:

go
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):

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

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

Getting all users (GET /users):

json
// 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:

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

Create an Echo application with Redis integration:

go
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):

json
// 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:

go
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:

go
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:

go
// 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.



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