Updated for nself v0.4.8
nself provides excellent support for Go services through custom service templates, enabling you to build high-performance, efficient microservices that integrate seamlessly with your nself stack. With the gin, echo, fiber, and grpc templates, automatic configuration, and production-ready deployment, Go services complement your Node.js and Python services with exceptional speed and resource efficiency.
Go services in nself provide:
Add Go services using the custom service (CS_N) format in your .env file:
# Custom Service Format: CS_N=name:template:port:route
# Available Go templates: gin, echo, fiber, grpc
# High-performance Gin API
CS_1=api:gin:8001:/api
# Minimal Echo microservice
CS_2=service:echo:8002
# Fast web service with Fiber
CS_3=web:fiber:8003
# gRPC service
CS_4=grpc-service:grpc:50051# Generate Go services from templates
nself build
# Start all services
nself startThis creates the following structure:
services/
├── api/ # Gin API service
│ ├── cmd/
│ │ └── main.go
│ ├── internal/
│ ├── pkg/
│ ├── go.mod
│ ├── go.sum
│ └── Dockerfile
├── service/ # Echo microservice
│ ├── main.go
│ ├── go.mod
│ └── Dockerfile
├── web/ # Fiber web service
│ ├── main.go
│ ├── go.mod
│ └── Dockerfile
└── grpc-service/ # gRPC service
├── main.go
├── proto/
├── go.mod
└── Dockerfile// cmd/main.go
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"time"
"github.com/gin-gonic/gin"
"your-app/internal/handlers"
"your-app/internal/middleware"
"your-app/pkg/database"
)
func main() {
// Initialize database connection
db, err := database.Connect()
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
defer db.Close()
// Setup router
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())
r.Use(middleware.CORS())
// Health check endpoint
r.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "healthy"})
})
// API routes
api := r.Group("/api/v1")
handlers.SetupRoutes(api, db)
// Graceful shutdown
srv := &http.Server{
Addr: ":8080",
Handler: r,
}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv.Shutdown(ctx)
}// pkg/database/postgres.go
package database
import (
"database/sql"
"fmt"
"os"
_ "github.com/lib/pq"
)
type DB struct {
*sql.DB
}
func Connect() (*DB, error) {
dsn := fmt.Sprintf(
"host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
os.Getenv("POSTGRES_HOST"),
os.Getenv("POSTGRES_PORT"),
os.Getenv("POSTGRES_USER"),
os.Getenv("POSTGRES_PASSWORD"),
os.Getenv("POSTGRES_DB"),
)
sqlDB, err := sql.Open("postgres", dsn)
if err != nil {
return nil, err
}
// Connection pool settings
sqlDB.SetMaxOpenConns(25)
sqlDB.SetMaxIdleConns(5)
sqlDB.SetConnMaxLifetime(5 * time.Minute)
if err := sqlDB.Ping(); err != nil {
return nil, err
}
return &DB{sqlDB}, nil
}
// User represents a user in the system
type User struct {
ID string `json:"id"`
Email string `json:"email"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
IsActive bool `json:"is_active"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (db *DB) GetUser(id string) (*User, error) {
user := &User{}
query := `
SELECT id, email, first_name, last_name, is_active, created_at, updated_at
FROM users WHERE id = $1
`
err := db.QueryRow(query, id).Scan(
&user.ID, &user.Email, &user.FirstName,
&user.LastName, &user.IsActive, &user.CreatedAt, &user.UpdatedAt,
)
if err != nil {
return nil, err
}
return user, nil
}// pkg/redis/client.go
package redis
import (
"context"
"encoding/json"
"os"
"time"
"github.com/go-redis/redis/v8"
)
type Client struct {
rdb *redis.Client
}
func NewClient() *Client {
rdb := redis.NewClient(&redis.Options{
Addr: os.Getenv("REDIS_HOST") + ":" + os.Getenv("REDIS_PORT"),
Password: os.Getenv("REDIS_PASSWORD"),
DB: 0,
})
return &Client{rdb: rdb}
}
func (c *Client) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
data, err := json.Marshal(value)
if err != nil {
return err
}
return c.rdb.Set(ctx, key, data, expiration).Err()
}
func (c *Client) Get(ctx context.Context, key string, dest interface{}) error {
data, err := c.rdb.Get(ctx, key).Result()
if err != nil {
return err
}
return json.Unmarshal([]byte(data), dest)
}
func (c *Client) Close() error {
return c.rdb.Close()
}// internal/websocket/hub.go
package websocket
import (
"log"
"net/http"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true // Configure for production
},
}
type Hub struct {
clients map[*Client]bool
broadcast chan []byte
register chan *Client
unregister chan *Client
}
type Client struct {
hub *Hub
conn *websocket.Conn
send chan []byte
}
func NewHub() *Hub {
return &Hub{
clients: make(map[*Client]bool),
broadcast: make(chan []byte),
register: make(chan *Client),
unregister: make(chan *Client),
}
}
func (h *Hub) Run() {
for {
select {
case client := <-h.register:
h.clients[client] = true
log.Printf("Client connected. Total: %d", len(h.clients))
case client := <-h.unregister:
if _, ok := h.clients[client]; ok {
delete(h.clients, client)
close(client.send)
log.Printf("Client disconnected. Total: %d", len(h.clients))
}
case message := <-h.broadcast:
for client := range h.clients {
select {
case client.send <- message:
default:
close(client.send)
delete(h.clients, client)
}
}
}
}
}
func (h *Hub) HandleWebSocket(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("WebSocket upgrade error: %v", err)
return
}
client := &Client{hub: h, conn: conn, send: make(chan []byte, 256)}
client.hub.register <- client
go client.writePump()
go client.readPump()
}// pkg/grpc/client.go
package grpc
import (
"context"
"time"
"google.golang.org/grpc"
pb "your-app/proto"
)
type Client struct {
conn *grpc.ClientConn
client pb.UserServiceClient
}
func NewClient(address string) (*Client, error) {
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
return nil, err
}
client := pb.NewUserServiceClient(conn)
return &Client{conn: conn, client: client}, nil
}
func (c *Client) GetUser(ctx context.Context, userID string) (*pb.User, error) {
ctx, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
req := &pb.GetUserRequest{UserId: userID}
return c.client.GetUser(ctx, req)
}
func (c *Client) Close() error {
return c.conn.Close()
}// pkg/http/client.go
package http
import (
"net/http"
"time"
)
func NewOptimizedClient() *http.Client {
transport := &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 90 * time.Second,
DisableKeepAlives: false,
}
return &http.Client{
Transport: transport,
Timeout: 30 * time.Second,
}
}// internal/handlers/streaming.go
package handlers
import (
"bufio"
"net/http"
"github.com/gin-gonic/gin"
)
func StreamDataHandler(c *gin.Context) {
c.Header("Content-Type", "application/json")
c.Header("Transfer-Encoding", "chunked")
writer := bufio.NewWriter(c.Writer)
defer writer.Flush()
// Stream data in chunks to avoid memory buildup
for i := 0; i < 1000; i++ {
data := generateData(i)
writer.WriteString(data + "\n")
// Flush periodically
if i%100 == 0 {
writer.Flush()
}
}
}# Build stage
FROM golang:1.21-alpine AS builder
WORKDIR /app
# Install dependencies
COPY go.mod go.sum ./
RUN go mod download
# Copy source and build
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main ./cmd/main.go
# Production stage
FROM alpine:latest
# Install CA certificates for HTTPS requests
RUN apk --no-cache add ca-certificates
WORKDIR /root/
# Copy the binary from builder stage
COPY --from=builder /app/main .
# Create non-root user
RUN adduser -D -s /bin/sh appuser
USER appuser
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
EXPOSE 8080
CMD ["./main"]// internal/handlers/users_test.go
package handlers
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"your-app/internal/mocks"
)
func TestGetUser(t *testing.T) {
gin.SetMode(gin.TestMode)
// Setup
mockDB := &mocks.Database{}
router := gin.New()
router.GET("/users/:id", GetUserHandler(mockDB))
// Mock data
expectedUser := &User{
ID: "123",
Email: "test@example.com",
}
mockDB.On("GetUser", "123").Return(expectedUser, nil)
// Test
req, _ := http.NewRequest("GET", "/users/123", nil)
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, req)
// Assertions
assert.Equal(t, http.StatusOK, recorder.Code)
var response User
json.Unmarshal(recorder.Body.Bytes(), &response)
assert.Equal(t, expectedUser.ID, response.ID)
assert.Equal(t, expectedUser.Email, response.Email)
mockDB.AssertExpectations(t)
}# Add a Go service via CS_N configuration
# In .env file:
CS_1=user-service:gin:8001:/users
# Rebuild to generate from template
nself build
# Start services
nself start
# View service logs
nself logs user-service
# Follow logs in real-time
nself logs user-service -f
# Check service status
nself statusNow that you understand Go services:
Go services provide exceptional performance and efficiency for your nself microservices architecture.