nself provides excellent support for Go services, enabling you to build high-performance, efficient microservices that integrate seamlessly with your nself stack. With automatic configuration, built-in templates, 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 to your .env.local
:
# Enable Go services
GOLANG_SERVICES=api-gateway,data-processor,websocket-server
# Optional: Specify Go version
GOLANG_VERSION=1.21
# Optional: Enable additional features
GOLANG_BUILD_TAGS=jsoniter,netgo
GOLANG_CGO_ENABLED=0
# Generate Go services
nself build
# Start services
nself up
This creates the following structure:
services/
├── golang/
│ ├── api-gateway/ # HTTP API Gateway
│ │ ├── cmd/
│ │ ├── internal/
│ │ ├── pkg/
│ │ ├── go.mod
│ │ ├── go.sum
│ │ └── Dockerfile
│ ├── data-processor/ # Data processing service
│ └── websocket-server/ # WebSocket service
└── shared/
└── go/ # Shared Go packages
├── database/
├── redis/
└── types/
// 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)
}
# Generate new Go service
nself generate golang --name user-service
# Build Go services
nself build golang --service api-gateway
# Run tests
nself test golang --service api-gateway
# View service logs
nself logs golang --service api-gateway --follow
# Hot reload during development
nself dev golang --service api-gateway
Now that you understand Go services:
Go services provide exceptional performance and efficiency for your nself microservices architecture.