Gin Basics #1 Getting Started and Your First Server

7 min read

This series is a 7-part introductory course for anyone who wants to build a web API in Go. If you picked up the syntax in the Go Basics series, this is the step where you use that syntax to write a real HTTP server.

  • #1 Getting started and your first server ← this post
  • #2 Routing and handlers
  • #3 Request binding and validation
  • #4 Responses — JSON, status codes, errors
  • #5 Middleware
  • #6 Database integration (GORM)
  • #7 Project structure and a mini REST API

This post points out where the standard library net/http alone falls short, then takes you through installing Gin and running your first server.

What is Gin? #

Gin is the most widely used web framework in Go. More precisely, it’s a lightweight framework centered on an HTTP router and middleware. It sits as a thin layer on top of the standard library net/http, so it keeps Go’s default behavior almost intact while cutting down on the repetitive work.

Its key characteristics:

  • Fast routing — a radix tree based router for fast path matching
  • Concise handlers — a single gin.Context handles both reading the request and writing the response
  • Middleware chain — cleanly wires up common processing like logging, recovery, and authentication
  • Convenience features — JSON binding, validation, and file responses come built in

Since it’s effectively the de facto standard choice in the Go community, it has the most resources and examples. Being able to easily find answers when you get stuck is a big advantage too.

Why isn’t net/http alone enough? #

Go can write an HTTP server with just the standard library. As of Go 1.22, the ServeMux in net/http supports method and path pattern matching. So for a simple server, you don’t need an external framework at all.

But as you build a real API, you end up repeating the same code over and over.

A JSON response with net/http
package main

import (
	"encoding/json"
	"net/http"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("GET /ping", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		json.NewEncoder(w).Encode(map[string]string{"message": "pong"})
	})
	http.ListenAndServe(":8080", mux)
}

Even just sending out one JSON response, you have to set the header, the status code, and the encoding by hand every time. Add reading path parameters, validating the request body, and applying common logic across multiple handlers, and the code gets verbose fast.

Gin reduces exactly this repetition. The same response written with Gin looks like this.

The same response with Gin
r.GET("/ping", func(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"message": "pong"})
})

A single line, c.JSON, handles the header, status code, and encoding all at once. The difference looks small, but once your endpoints grow to dozens, the accumulated difference is large.

Compared to other Go web frameworks #

There are options besides Gin, so let me sketch out roughly where each sits.

GinEchoFiberChi
Basenet/httpnet/httpfasthttpnet/http
CharacterRouter + middlewareRouter + middlewareExpress styleThin router
EcosystemLargestLargeLargeStandard-friendly
Learning curveLowLowLowLowest

Echo sits in almost the same position as Gin. Fiber sits on top of fasthttp rather than net/http, gaining more performance at the cost of giving up some standard compatibility. Chi is the thin router most faithful to the standard net/http. If you’re just starting out, I recommend you start with Gin, which has the most resources, and look at the other options when you need them.

Preparation — Go modules #

Gin is a Go package, so you need Go installed first. If you haven’t installed it yet, read Go Basics #1 first. This series assumes Go 1.22 or later.

Create a new project and start a module.

New project
mkdir gin-hello
cd gin-hello
go mod init gin-hello

go mod init creates a go.mod file and starts a new module. The module concept was covered in the Go Basics series, so here we’ll just move ahead with it.

Installing Gin #

Add Gin to your dependencies with go get.

Install Gin
go get github.com/gin-gonic/gin

This series is written against Gin v1.10. Installing it records the dependency in go.mod and leaves a checksum in go.sum. Include both in version control (git).

Your first server — Hello, Gin #

Create a main.go file with the following.

main.go
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()

	r.GET("/ping", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "pong"})
	})

	r.Run() // default :8080
}

Run it.

Run
go run main.go

Once the server is up, send a request from another terminal.

Request
curl http://localhost:8080/ping
# {"message":"pong"}

Opening http://localhost:8080/ping in a browser shows the same JSON. Your first Gin server is working.

Walking through the code line by line #

main.go again
r := gin.Default()

r.GET("/ping", func(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"message": "pong"})
})

r.Run()
  • gin.Default() — creates the router (engine). The logging and recovery middleware are attached by default.
  • r.GET("/ping", ...) — registers the handler to run when a GET request comes in on the /ping path.
  • func(c *gin.Context) — the shape of a handler. All request information and response capabilities are packed into a single gin.Context.
  • c.JSON(...) — takes a status code and data and responds as JSON. gin.H is shorthand for map[string]any.
  • r.Run() — starts the server. With no argument it listens on :8080. You can also specify a port, like r.Run(":3000").

Notice that we used http.StatusOK instead of a bare number like 200 for the status code. The meaning becomes clear, and the risk of entering the wrong code due to a typo goes down. This series follows the practice of using the standard constants.

gin.Default() vs gin.New() #

There are two ways to create a router.

Two ways to create a router
r := gin.Default() // includes Logger + Recovery middleware
r := gin.New()     // an empty engine with no middleware

gin.Default() automatically attaches the Logger middleware, which prints request logs, and the Recovery middleware, which keeps the server from dying even when a handler panics. For the introductory stage, gin.Default() is plenty.

gin.New() is an empty engine with no middleware at all. Use it when you want to directly control which middleware to attach. Middleware is covered in detail in #5 Middleware.

Release mode #

The first time you start the server, you’ll see the following warning in the console.

Development mode warning
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.

Gin defaults to debug mode, so it prints debug information like the list of registered routes. In a real deployment environment, you switch to release mode to cut down the unnecessary logs.

Release mode
gin.SetMode(gin.ReleaseMode)

It can also be set via the environment variable GIN_MODE=release. For now, since you’re in development, it’s fine to leave it in debug mode. Deployment-related settings will be revisited in the intermediate series.

Hot reload — auto restart on save #

Re-running go run every time you edit your code is tedious. A tool like air automatically rebuilds and restarts the server whenever a file changes.

Installing and running air
go install github.com/air-verse/air@latest
air

It isn’t required, but it greatly improves the development experience. You can follow along with this series fine even without installing it.

Wrapping up #

What this post covered:

  • Gin is the most widely used web framework in Go, sitting as a thin layer on top of net/http
  • A server works with just the standard library, but Gin reduces the repetition of JSON responses, validation, and common processing
  • Install with go get github.com/gin-gonic/gin; the series is based on Gin v1.10
  • Create a router with gin.Default(), register handlers with r.GET, and start with r.Run()
  • A response is a single line: c.JSON(http.StatusOK, gin.H{...})
  • Use standard constants like http.StatusOK for status codes
  • For hot reload during development, air is convenient

In the next post (#2 Routing and handlers), we’ll cover how to read path parameters and query strings, and how to group endpoints with router groups.

X