Chi #

Chi adalah router HTTP untuk Go yang ringan, idiomatic, dan sangat dekat dengan net/http standar. Tidak seperti Gin atau Fiber yang membawa abstraksi framework sendiri, Chi hanya menambahkan routing, middleware composition, dan helper kecil di atas http.Handler standar. Hasilnya: code yang kamu tulis dengan Chi tetaplah code Go yang bisa dipahami developer mana pun — tanpa kurva belajar framework baru.

Untuk tim yang menghargai kesederhanaan, kontainabilitas, dan kompatibilitas dengan ekosistem net/http (OpenTelemetry, prometheus, middleware populer), Chi adalah pilihan yang sangat solid. Artikel ini membahas setup Docker Compose untuk local development Chi, dari Dockerfile multi-stage, hot reload dengan Air, hingga integrasi database dan best practice.

Prasyarat #

Pastikan sudah terinstall:

  • Docker dan Docker Compose versi terbaru
  • Go 1.22+ (opsional, untuk development di host)
  • Git

Struktur project Chi standar:

my-chi-app/
├── cmd/
│   └── api/
│       └── main.go
├── internal/
│   ├── http/
│   │   ├── router.go
│   │   └── middleware.go
│   ├── handler/
│   │   └── user.go
│   ├── service/
│   │   └── user.go
│   ├── repository/
│   │   └── user.go
│   └── model/
│       └── user.go
├── go.mod
├── go.sum
├── Dockerfile
├── Dockerfile.dev
├── docker-compose.yml
├── .air.toml
├── .env
└── .dockerignore

Pemisahan direktori internal/http/ untuk routing+middleware dan internal/handler/ untuk handler endpoint adalah konvensi yang jelas — saat ada developer baru, mereka langsung tahu di mana mencari kode untuk routing versus business logic.

Apa yang Membuat Chi Berbeda #

Sebelum masuk ke teknis, pahami dulu posisi Chi di ekosistem Go.

Framework Pendekatan Kompabilitas net/http
net/http standar Minimal, http.ServeMux Native
Chi Router di atas http.Handler 100% kompatibel
Gin Framework dengan *gin.Context Partial, adapter untuk middleware
Fiber Framework dengan *fiber.Ctx di atas fasthttp Tidak kompatibel

Karena Chi adalah http.Handler (bukan wrapper), semua middleware ekosistem Go bisa langsung dipakai:

// Middleware dari third-party yang langsung jalan di Chi
import "github.com/go-chi/cors"
import "github.com/go-chi/httprate"
import "github.com/prometheus/client_golang/prometheus/promhttp"

r.Use(cors.Handler(cors.Options{...}))
r.Use(httprate.LimitByIP(100, time.Minute))
r.Handle("/metrics", promhttp.Handler())

Tidak perlu adaptor, tidak perlu cors.New() constructor yang berbeda, tidak ada kebingungan apakah middleware ini “bekerja di framework ini”. Semuanya jalan.

Dockerfile Multi-Stage #

Pattern multi-stage standar untuk Go.

# syntax=docker/dockerfile:1.6
FROM golang:1.22-alpine AS builder

WORKDIR /app

RUN apk add --no-cache git ca-certificates

COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /out/api ./cmd/api

FROM alpine:3.19

RUN apk add --no-cache ca-certificates tzdata
WORKDIR /app

RUN addgroup -S app && adduser -S app -G app
USER app:app

COPY --from=builder /out/api /app/api

EXPOSE 8080

ENTRYPOINT ["/app/api"]

Image akhir ramping, < 20 MB, berbasis alpine.

Dockerfile untuk Development #

# Dockerfile.dev
FROM golang:1.22-alpine

WORKDIR /app

RUN apk add --no-cache git curl bash

# Install Air
RUN go install github.com/air-verse/air@latest

COPY go.mod go.sum ./
RUN go mod download

EXPOSE 8080

CMD ["air", "-c", ".air.toml"]

Konfigurasi Air (.air.toml):

root = "."
tmp_dir = "tmp"

[build]
  bin = "./tmp/main"
  cmd = "go build -o ./tmp/main ./cmd/api"
  delay = 1000
  exclude_dir = ["assets", "tmp", "vendor", "testdata"]
  include_dir = ["cmd", "internal"]
  include_ext = ["go", "yaml", "yml", "tmpl", "html"]
  exclude_regex = ["_test.go"]
  exclude_unchanged = true

[log]
  time = true

docker-compose.yml #

# docker-compose.yml
services:
  api:
    build:
      context: .
      dockerfile: Dockerfile.dev
    image: chi-app:dev
    container_name: chi-api
    ports:
      - "8080:8080"
    volumes:
      - ./cmd:/app/cmd
      - ./internal:/app/internal
      - ./go.mod:/app/go.mod
      - ./go.sum:/app/go.sum
      - go-build:/app/tmp
    environment:
      - APP_ENV=development
      - SERVER_PORT=8080
      - DATABASE_URL=postgres://app:dev@db:5432/chiapp?sslmode=disable
      - REDIS_URL=redis://cache:6379/0
      - JWT_SECRET=local-dev-secret-change-me
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_healthy
    command: air -c .air.toml

  db:
    image: postgres:16-alpine
    container_name: chi-db
    environment:
      - POSTGRES_USER=app
      - POSTGRES_PASSWORD=dev
      - POSTGRES_DB=chiapp
    volumes:
      - db-data:/var/lib/postgresql/data
      - ./db/init:/docker-entrypoint-initdb.d:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d chiapp"]
      interval: 10s
      timeout: 5s
      retries: 5
    ports:
      - "5432:5432"

  cache:
    image: redis:7-alpine
    container_name: chi-cache
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3
    volumes:
      - cache-data:/data
    ports:
      - "6379:6379"

volumes:
  db-data:
  cache-data:
  go-build:

Contoh Aplikasi Chi #

Router (internal/http/router.go):

package http

import (
    "net/http"
    "time"

    "github.com/go-chi/chi/v5"
    "github.com/go-chi/chi/v5/middleware"
    "github.com/go-chi/cors"
    "github.com/go-chi/httprate"

    "myapp/internal/handler"
)

type Router struct {
    *chi.Mux
    userHandler *handler.UserHandler
}

func NewRouter(userHandler *handler.UserHandler) *Router {
    r := chi.NewRouter()

    // Built-in middleware Chi
    r.Use(middleware.RequestID)
    r.Use(middleware.RealIP)
    r.Use(middleware.Logger)
    r.Use(middleware.Recoverer)
    r.Use(middleware.Timeout(60 * time.Second))

    // CORS
    r.Use(cors.Handler(cors.Options{
        AllowedOrigins:   []string{"*"},
        AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
        AllowedHeaders:   []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
        ExposedHeaders:   []string{"Link"},
        AllowCredentials: false,
        MaxAge:           300,
    }))

    // Rate limit
    r.Use(httprate.LimitByIP(100, time.Minute))

    // Routes
    r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        w.Write([]byte(`{"status":"ok"}`))
    })

    r.Route("/api/v1", func(r chi.Router) {
        r.Route("/users", func(r chi.Router) {
            r.Post("/", userHandler.Create)
            r.Get("/{id}", userHandler.Get)
            r.Get("/", userHandler.List)
        })
    })

    return &Router{Mux: r, userHandler: userHandler}
}

Handler (internal/handler/user.go):

package handler

import (
    "encoding/json"
    "errors"
    "net/http"
    "strconv"

    "github.com/go-chi/chi/v5"

    "myapp/internal/service"
)

type UserHandler struct {
    svc *service.UserService
}

func NewUserHandler(svc *service.UserService) *UserHandler {
    return &UserHandler{svc: svc}
}

type createUserRequest struct {
    Email    string `json:"email"`
    Name     string `json:"name"`
    Password string `json:"password"`
}

type errorResponse struct {
    Error string `json:"error"`
}

func writeJSON(w http.ResponseWriter, code int, payload interface{}) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(code)
    json.NewEncoder(w).Encode(payload)
}

func (h *UserHandler) Create(w http.ResponseWriter, r *http.Request) {
    var req createUserRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        writeJSON(w, http.StatusBadRequest, errorResponse{Error: "invalid body"})
        return
    }
    if req.Email == "" || req.Name == "" || len(req.Password) < 8 {
        writeJSON(w, http.StatusBadRequest, errorResponse{Error: "email, name, password (min 8) required"})
        return
    }
    u, err := h.svc.Register(r.Context(), req.Email, req.Name, req.Password)
    if err != nil {
        writeJSON(w, http.StatusBadRequest, errorResponse{Error: err.Error()})
        return
    }
    writeJSON(w, http.StatusCreated, u)
}

func (h *UserHandler) Get(w http.ResponseWriter, r *http.Request) {
    idStr := chi.URLParam(r, "id")
    id, err := strconv.ParseUint(idStr, 10, 64)
    if err != nil {
        writeJSON(w, http.StatusBadRequest, errorResponse{Error: "invalid id"})
        return
    }
    u, err := h.svc.Get(r.Context(), id)
    if err != nil {
        writeJSON(w, http.StatusInternalServerError, errorResponse{Error: err.Error()})
        return
    }
    if u == nil {
        writeJSON(w, http.StatusNotFound, errorResponse{Error: "user not found"})
        return
    }
    writeJSON(w, http.StatusOK, u)
}

func (h *UserHandler) List(w http.ResponseWriter, r *http.Request) {
    limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
    offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
    if limit == 0 {
        limit = 20
    }
    users, err := h.svc.List(r.Context(), limit, offset)
    if err != nil {
        writeJSON(w, http.StatusInternalServerError, errorResponse{Error: err.Error()})
        return
    }
    writeJSON(w, http.StatusOK, map[string]interface{}{
        "data":   users,
        "limit":  limit,
        "offset": offset,
    })
}

Service (internal/service/user.go):

package service

import (
    "context"
    "errors"
    "strings"

    "golang.org/x/crypto/bcrypt"

    "myapp/internal/model"
    "myapp/internal/repository"
)

type UserService struct {
    repo *repository.UserRepository
}

func NewUserService(repo *repository.UserRepository) *UserService {
    return &UserService{repo: repo}
}

func (s *UserService) Register(ctx context.Context, email, name, password string) (*model.User, error) {
    email = strings.ToLower(strings.TrimSpace(email))
    existing, _ := s.repo.FindByEmail(ctx, email)
    if existing != nil {
        return nil, errors.New("email already registered")
    }
    hashed, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
    if err != nil {
        return nil, err
    }
    u := &model.User{
        Email:    email,
        Name:     strings.TrimSpace(name),
        Password: string(hashed),
    }
    if err := s.repo.Create(ctx, u); err != nil {
        return nil, err
    }
    return u, nil
}

func (s *UserService) Get(ctx context.Context, id uint64) (*model.User, error) {
    return s.repo.FindByID(ctx, id)
}

func (s *UserService) List(ctx context.Context, limit, offset int) ([]model.User, error) {
    return s.repo.List(ctx, limit, offset)
}

cmd/api/main.go:

package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"

    "github.com/redis/go-redis/v9"
    "gorm.io/driver/postgres"
    "gorm.io/gorm"

    "myapp/internal/handler"
    httpx "myapp/internal/http"
    "myapp/internal/model"
    "myapp/internal/repository"
    "myapp/internal/service"
)

func main() {
    // Database dengan retry
    var db *gorm.DB
    var err error
    dsn := os.Getenv("DATABASE_URL")
    for i := 0; i < 30; i++ {
        db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
        if err == nil {
            break
        }
        log.Printf("waiting for db: %v", err)
        time.Sleep(2 * time.Second)
    }
    if err != nil {
        log.Fatal("db connection failed: ", err)
    }
    if err := db.AutoMigrate(&model.User{}); err != nil {
        log.Fatal("migrate failed: ", err)
    }

    // Redis
    rdb := redis.NewClient(&redis.Options{Addr: os.Getenv("REDIS_URL")})
    _ = rdb

    // Layers
    userRepo := repository.NewUserRepository(db)
    userSvc := service.NewUserService(userRepo)
    userHandler := handler.NewUserHandler(userSvc)

    // Router
    r := httpx.NewRouter(userHandler)

    srv := &http.Server{
        Addr:         ":8080",
        Handler:      r,
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 10 * time.Second,
    }

    // Graceful shutdown
    go func() {
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Printf("server stopped: %v", err)
        }
    }()
    log.Printf("listening on %s", srv.Addr)

    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit
    log.Println("shutting down...")

    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    if err := srv.Shutdown(ctx); err != nil {
        log.Fatal(err)
    }
}

Middleware Bawaan Chi #

Chi punya paket middleware resmi yang sangat lengkap.

Middleware Fungsi
RequestID Generate/extract X-Request-ID
RealIP Ambil IP asli dari X-Forwarded-For
Logger Log setiap request
Recoverer Tangkap panic, return 500
Timeout Timeout handler setelah durasi tertentu
URLFormat Format URL: /users.{format}
Compress Gzip response
ContentCharset Validasi Content-Type charset
GetHead Auto-handle HEAD untuk GET
StripSlashes Hapus trailing slash
Heartbeat Endpoint /healthz untuk liveness
Throttle Batasi concurrent request
WithValue Inject value ke context

Kombinasi yang aman untuk production API:

r := chi.NewRouter()
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.Timeout(60 * time.Second))
r.Use(middleware.Compress(5, "gzip"))
r.Use(middleware.Heartbeat("/healthz"))

RequestID paling awal agar ID bisa di-log. RealIP sebelum Logger agar IP yang di-log adalah IP asli. Recoverer sebelum Logger agar panic juga ter-log. Timeout di akhir agar timeout mencakup handler, bukan middleware.

Routing Kompleks dengan Chi #

Salah satu kekuatan Chi adalah grouping dan sub-router.

r := chi.NewRouter()

// Public routes
r.Get("/", listLanding)
r.Get("/healthz", healthz)
r.Handle("/metrics", promhttp.Handler())

// API versioning
r.Route("/api/v1", func(r chi.Router) {
    // Public endpoints
    r.Group(func(r chi.Router) {
        r.Post("/auth/login", authHandler.Login)
        r.Post("/auth/register", authHandler.Register)
    })

    // Protected endpoints
    r.Group(func(r chi.Router) {
        r.Use(authMiddleware)

        r.Route("/users", func(r chi.Router) {
            r.Get("/", userHandler.List)
            r.Post("/", userHandler.Create)
            r.Route("/{id}", func(r chi.Router) {
                r.Get("/", userHandler.Get)
                r.Put("/", userHandler.Update)
                r.Delete("/", userHandler.Delete)
            })
        })

        r.Route("/posts", func(r chi.Router) {
            r.Get("/", postHandler.List)
            r.Post("/", postHandler.Create)
        })
    })
})

Pola r.Route("/path", func(r chi.Router) { ... }) membuat sub-router dengan path prefix sendiri. r.Group(func(r chi.Router) { ... }) membuat group dengan middleware tambahan tanpa mengubah path. Kombinasi keduanya menghasilkan struktur route yang sangat ekspresif.

Middleware Kustom #

Karena Chi hanya menambahkan routing di atas http.Handler, middleware kustom sangat mudah ditulis.

package http

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

type ctxKey string

const (
    ctxKeyRequestID ctxKey = "requestID"
    ctxKeyUserID    ctxKey = "userID"
)

func RequestTime(header string) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            start := time.Now()
            next.ServeHTTP(w, r)
            w.Header().Set(header, time.Since(start).String())
        })
    }
}

func RequireAuth(secret string) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            token := r.Header.Get("Authorization")
            if token == "" {
                http.Error(w, "missing token", http.StatusUnauthorized)
                return
            }
            // validasi token (JWT, session, dll)
            userID, err := validateToken(token, secret)
            if err != nil {
                http.Error(w, "invalid token", http.StatusUnauthorized)
                return
            }
            ctx := context.WithValue(r.Context(), ctxKeyUserID, userID)
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

// Pakai
r.Use(RequestTime("X-Response-Time"))
r.Group(func(r chi.Router) {
    r.Use(RequireAuth(os.Getenv("JWT_SECRET")))
    r.Get("/me", meHandler)
})

Fungsi middleware Chi mengikuti pola func(http.Handler) http.Handler — sama persis dengan standar library. Ini membuat mereka kompatibel dengan framework Go lain.

Build dan Run #

# Build image
docker compose build

# Jalankan
docker compose up -d

# Lihat log
docker compose logs -f api

# Stop
docker compose down

Akses:

  • API: http://localhost:8080
  • PostgreSQL: localhost:5432
  • Redis: localhost:6379

Test:

# Register
curl -X POST http://localhost:8080/api/v1/users \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","name":"Andi","password":"password123"}'

# Get user
curl http://localhost:8080/api/v1/users/1

# List users
curl "http://localhost:8080/api/v1/users?limit=10&offset=0"

OpenTelemetry dan Prometheus #

Karena Chi adalah http.Handler, integrasi observability standar Go langsung jalan.

import (
    "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

r := chi.NewRouter()

// OpenTelemetry: instrument semua handler
r.Use(otelhttp.Middleware("my-app"))

// Prometheus: expose metrics
r.Handle("/metrics", promhttp.Handler())

Untuk tracing span custom:

func (h *UserHandler) Get(w http.ResponseWriter, r *http.Request) {
    tracer := otel.Tracer("myapp/handler")
    ctx, span := tracer.Start(r.Context(), "UserHandler.Get")
    defer span.End()

    // ... business logic
    span.SetAttributes(attribute.Int64("user.id", int64(id)))
}

Span otomatis dikirim ke Jaeger/Tempo/Honeycomb. Tanpa instrumentasi custom, setiap HTTP request sudah di-trace lewat otelhttp.Middleware.

Kapan Chi Tepat #

Pakai Chi jika:
  ✓ Mau router ringan, bukan full framework
  ✓ Butuh kompatibilitas net/http 100%
  ✓ Pakai middleware ekosistem Go standar
  ✓ Microservice yang sangat minimal
  ✓ Tim ingin belajar Go idiomatic

Hindari Chi jika:
  ✗ Butuh fitur framework lengkap (validation, ORM, dll)
  ✗ Suka API yang deklaratif seperti Express
  ✗ Butuh performa lebih tinggi dari stdlib (Gin/fasthttp)

Chi jujur tentang apa dirinya: router, bukan framework. Ia tidak membawa ORM, validator, atau template engine. Itu semua dependency terpisah yang kamu pilih. Untuk proyek yang ingin “stay close to the metal” tapi tetap punya routing yang powerful, Chi adalah sweet spot.

Best Practice #

Cache Dependency Module #

Selalu copy go.mod + go.sum lebih dulu. Build inkremental super cepat.

Pakai Air untuk Hot Reload #

Tanpa hot reload, Go terasa lambat untuk iterasi. Air memantau file dan rebuild otomatis.

Pakai r.Route dan r.Group untuk Organisasi #

Jangan tulis semua route flat di satu fungsi. Group berdasarkan resource (users, posts) dan versi (v1, v2). Middleware bisa dipasang per-group.

Manfaatkan Middleware Ekosistem #

Chi kompatibel dengan middleware net/http manapun. Pakai library populer:

  • github.com/go-chi/cors untuk CORS
  • github.com/go-chi/httprate untuk rate limit
  • github.com/prometheus/client_golang untuk metrics
  • go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp untuk tracing

Tulis Middleware Kustom untuk Hal Spesifik #

Butuh request time logging? Tulis middleware sendiri. Butuh feature flag? Tulis middleware. Pola func(http.Handler) http.Handler sederhana dan idiom Go.

Bind Mount Source Code di Dev #

Pakai volumes di Compose untuk mount source code ke container. Container pakai Air untuk rebuild.

Healthcheck untuk Dependency #

PostgreSQL dan Redis harus punya healthcheck. Service api pakai depends_on: condition: service_healthy. Tambahkan retry loop di kode Go.

Troubleshooting #

Air Tidak Detect Perubahan #

Pastikan bind mount include direktori cmd/ dan internal/. Naikkan delay di .air.toml kalau rebuild terlalu sering.

Database Connection Refused #

Pakai depends_on: condition: service_healthy di Compose, dan tambahkan retry loop di kode Go.

Chi Middleware Tidak Jalan #

Pastikan middleware di-register dengan r.Use() sebelum route. r.Use() setelah r.Get() tidak akan mengeksekusi middleware untuk route yang dideklarasikan sebelumnya.

Chi vs net/http Standar #

Go 1.22+ memperkenalkan enhanced http.ServeMux dengan pattern matching dan method routing. Untuk service yang sangat minimal, pertimbangkan net/http murni dulu. Tambahkan Chi hanya kalau butuh grouping, middleware composition, atau sub-router.

Ringkasan #

  • Chi ideal untuk service Go yang ingin tetap idiomatic dan dekat dengan net/http standar.
  • Chi adalah router, bukan framework — tidak membawa ORM, validator, atau template engine. Pilih dependency sendiri sesuai kebutuhan.
  • Kompabilitas net/http 100% — middleware ekosistem Go standar (cors, httprate, prometheus, otelhttp) langsung jalan tanpa adaptor.
  • Dockerfile multi-stage untuk production: builder stage dengan Go toolchain, runtime stage ramping. Image akhir < 20 MB.
  • Dockerfile.dev untuk development: full toolchain + Air untuk hot reload.
  • Hot reload dengan Air: pantau file .go, rebuild, restart. Konfigurasi lewat .air.toml.
  • Layer cache Go: copy go.mod + go.sum dulu, jalankan go mod download, baru copy source.
  • Routing ekspresif dengan r.Route() untuk sub-router dan r.Group() untuk middleware group.
  • Middleware built-in: RequestID, RealIP, Logger, Recoverer, Timeout, Compress, Heartbeat, Throttle.
  • Middleware kustom mengikuti pola func(http.Handler) http.Handler — standar library.
  • Observability: OpenTelemetry lewat otelhttp.Middleware, Prometheus lewat promhttp.Handler(). Tanpa konfigurasi khusus, langsung jalan.
  • Pakai Chi jika mau router ringan dengan kompatibilitas net/http 100%. Hindari jika butuh fitur framework lengkap atau API deklaratif.
  • Best practice: cache dependency, hot reload, organize routes dengan r.Route/r.Group, manfaatkan middleware ekosistem, tulis middleware kustom untuk hal spesifik.
  • Alternatif: Gin untuk full framework, Fiber untuk API Express-like, net/http standar untuk service paling minimal.

← Sebelumnya: Fiber   Berikutnya: Django →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact