If you want to split your app into microservices but don’t want to overcomplicate things, this guide is for you. I’ll walk through how to build simple, clean microservices using Go and Echo, a lightweight and super fast web framework. We’ll use Docker Compose for containerization so you can run everything locally.
By the end of this, you’ll have two basic services: one for products, one for orders. They’ll talk to each other, handle versioned CRUD APIs, and be easy to run, test, and grow later.
What are Microservices?
Microservices are small, independently deployable services, each responsible for a single business capability. Unlike monolithic applications, microservices facilitate easier scaling, independent deployments, fault isolation, and faster iterations.
Microservices architecture has rapidly become an industry standard for building scalable, maintainable, and highly resilient applications.
Why Go and Echo for Microservices?
Go Language (Golang), known for its simplicity, blazing performance, and powerful concurrency model, is an ideal choice for implementing microservices. Coupled with Echo, a high-performance and minimalist Go web framework, developers can build efficient, clean, and scalable RESTful APIs with minimal overhead.
7 Steps to Build Microservices in Go with Echo
Step 1: Set Up the Project
Let’s create the project folder and set things up:
mkdir microservices-echo
cd microservices-echo
go mod init github.com/yourusername/microservices-echo
go get github.com/labstack/echo/v4
Step 2: Organize the Files
We’ll split it into two service
microservices-echo
├── product-service
│ └── cmd
│ └── main.go
└── order-service
└── cmd
└── main.go
Step 3: Build the Product Service (CRUD + Versioning)
This service handles product data. It can create, update, fetch, and delete a product. And we’ll version the API from the start (so we can improve it later without breaking stuff).
Endpoints:
GET /api/v1/products/:id
- Retrieves product details.POST /api/v1/products
- Creates a new product.PUT /api/v1/products/:id
- Updates an existing product.DELETE /api/v1/products/:id
- Deletes a product.
package main
import (
"net/http"
"github.com/labstack/echo/v4"
)
func main() {
e := echo.New()
v1 := e.Group("/api/v1")
v1.GET("/products/:id", getProduct)
v1.POST("/products", createProduct)
v1.PUT("/products/:id", updateProduct)
v1.DELETE("/products/:id", deleteProduct)
e.Logger.Fatal(e.Start(":8081"))
}
func getProduct(c echo.Context) error {
id := c.Param("id")
return c.JSON(http.StatusOK, map[string]interface{}{"product_id": id, "name": "Product Name", "inventory": 100})
}
func createProduct(c echo.Context) error {
return c.JSON(http.StatusCreated, map[string]string{"status": "product created"})
}
func updateProduct(c echo.Context) error {
id := c.Param("id")
return c.JSON(http.StatusOK, map[string]string{"status": "product updated", "product_id": id})
}
func deleteProduct(c echo.Context) error {
id := c.Param("id")
return c.JSON(http.StatusOK, map[string]string{"status": "product deleted", "product_id": id})
}
Step 4: Build the Order Service (Talks to Product Service)
Now let’s make the Order Service. When someone places an order, it checks with the Product Service and updates the inventory.
Endpoints:
POST /api/v1/orders
- Creates a new order and updates product inventory.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/labstack/echo/v4"
)
func main() {
e := echo.New()
v1 := e.Group("/api/v1")
v1.POST("/orders", createOrder)
e.Logger.Fatal(e.Start(":8082"))
}
type OrderRequest struct {
ProductID string `json:"product_id"`
Quantity int `json:"quantity"`
}
func createOrder(c echo.Context) error {
order := new(OrderRequest)
if err := c.Bind(order); err != nil {
return c.JSON(http.StatusBadRequest, err)
}
resp, err := http.Get(fmt.Sprintf("http://product-service:8081/api/v1/products/%s", order.ProductID))
if err != nil {
return c.JSON(http.StatusInternalServerError, err)
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
inventoryUpdate := map[string]int{"inventory": -order.Quantity}
updateBody, _ := json.Marshal(inventoryUpdate)
_, err = http.Post(fmt.Sprintf("http://product-service:8081/api/v1/products/%s", order.ProductID), "application/json", bytes.NewBuffer(updateBody))
if err != nil {
return c.JSON(http.StatusInternalServerError, err)
}
return c.JSON(http.StatusOK, map[string]interface{}{
"order_status": "created",
"product_details": json.RawMessage(body),
})
}
Step 5: Docker Compose Setup
We’ll use Docker Compose to spin up both services with one command: docker-compose.yml
. Put this at the project root:
version: '3.8'
services:
product-service:
build: ./product-service
ports:
- "8081:8081"
order-service:
build: ./order-service
ports:
- "8082:8082"
depends_on:
- product-service
Each service will need a Dockerfile
:
FROM golang:1.21-alpine
WORKDIR /app
COPY . .
RUN go mod tidy
RUN go build -o main ./cmd/main.go
EXPOSE 8081
CMD ["./main"]
For Order Service, change EXPOSE
to 8082
.
Step 6: Run Everything
Time to see it in action:
docker-compose up --build
Now both services are live.
Step 7: Test Communication
Use this curl
command to test placing an order:
curl -X POST -H "Content-Type: application/json" -d '{"product_id": "123", "quantity": 5}' http://localhost:8082/api/v1/orders
You get something like this back:
{
"order_status": "created",
"product_details": {"product_id": "123", "name": "Product Name", "inventory": "100"}
}
Conclusion
That’s it! You now have two basic microservices built in Go with Echo, communicating with each other and running in Docker. It’s a solid starting point if you’re learning microservices or just want to build something small and clean.
From here, you can:
- Add a real database
- Improve error handling
- Use a service discovery tool
- Add auth or API gateway
- Deploy to the cloud
But for now, you’ve got the foundation set. Keep building, keep learning — and most importantly, keep it simple.
>>> Follow and Contact Relia Software for more information!
- golang
- coding
- web development