Skip to main content

Gin Status Codes

Introduction

When building web applications or APIs with Gin, communicating the result of a request is essential for both clients and developers. HTTP status codes are standardized numerical codes that indicate the outcome of an HTTP request. In Gin, handling these status codes properly ensures your API behaves predictably and follows web standards.

This guide will walk you through working with HTTP status codes in the Gin framework, explaining how to set them correctly and use them effectively to communicate with clients consuming your API.

Understanding HTTP Status Codes

HTTP status codes are three-digit numbers grouped into five categories:

  • 1xx (Informational): The request was received and the process is continuing
  • 2xx (Success): The request was successfully received, understood, and accepted
  • 3xx (Redirection): Further action is needed to complete the request
  • 4xx (Client Error): The request contains bad syntax or cannot be fulfilled
  • 5xx (Server Error): The server failed to fulfill a valid request

In Gin, you have full control over which status codes to send back to clients.

Setting Status Codes in Gin

Basic Usage

The simplest way to set a status code in Gin is using the c.Status() method:

go
func handleRequest(c *gin.Context) {
// Set HTTP status code to 200 (OK)
c.Status(http.StatusOK)
}

More commonly, you'll combine the status code with a response body:

go
func handleRequest(c *gin.Context) {
// Set status and return JSON response
c.JSON(http.StatusOK, gin.H{
"message": "Operation successful",
})
}

Common Status Code Methods

Gin provides several convenience methods for common status codes:

go
func handleResponse(c *gin.Context) {
// Success responses
c.JSON(http.StatusOK, gin.H{"message": "Success"}) // 200
c.JSON(http.StatusCreated, gin.H{"message": "Resource created"}) // 201

// Client error responses
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input"}) // 400
c.JSON(http.StatusUnauthorized, gin.H{"error": "Please login"}) // 401
c.JSON(http.StatusForbidden, gin.H{"error": "Access denied"}) // 403
c.JSON(http.StatusNotFound, gin.H{"error": "Resource not found"}) // 404

// Server error responses
c.JSON(http.StatusInternalServerError, gin.H{"error": "Server error"}) // 500
}

Practical Examples

Example 1: Basic CRUD API with Status Codes

Here's how you might implement a simple user API with appropriate status codes:

go
package main

import (
"net/http"
"github.com/gin-gonic/gin"
)

type User struct {
ID string `json:"id"`
Name string `json:"name"`
}

var users = []User{
{ID: "1", Name: "Alice"},
{ID: "2", Name: "Bob"},
}

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

// GET all users
router.GET("/users", func(c *gin.Context) {
c.JSON(http.StatusOK, users)
})

// GET a specific user
router.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")

for _, user := range users {
if user.ID == id {
c.JSON(http.StatusOK, user)
return
}
}

c.JSON(http.StatusNotFound, gin.H{
"error": "User not found",
})
})

// POST a new user
router.POST("/users", func(c *gin.Context) {
var newUser User

if err := c.BindJSON(&newUser); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Invalid user data",
})
return
}

// Check if ID is provided
if newUser.ID == "" {
c.JSON(http.StatusBadRequest, gin.H{
"error": "User ID is required",
})
return
}

users = append(users, newUser)
c.JSON(http.StatusCreated, newUser)
})

router.Run(":8080")
}

Example 2: Custom Error Handling

For more consistent error handling, you can create helper functions:

go
package main

import (
"net/http"
"github.com/gin-gonic/gin"
)

// Error response structure
type ErrorResponse struct {
Code int `json:"code"`
Message string `json:"message"`
}

// Success response structure
type SuccessResponse struct {
Code int `json:"code"`
Data interface{} `json:"data"`
}

// Helper function for error responses
func respondWithError(c *gin.Context, code int, message string) {
c.JSON(code, ErrorResponse{
Code: code,
Message: message,
})
}

// Helper function for success responses
func respondWithSuccess(c *gin.Context, data interface{}) {
c.JSON(http.StatusOK, SuccessResponse{
Code: http.StatusOK,
Data: data,
})
}

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

router.GET("/products/:id", func(c *gin.Context) {
id := c.Param("id")

// Simulating database lookup failure
if id == "999" {
respondWithError(c, http.StatusInternalServerError, "Database error")
return
}

// Simulating product not found
if id == "0" {
respondWithError(c, http.StatusNotFound, "Product not found")
return
}

// Return mock product data
product := gin.H{
"id": id,
"name": "Sample Product",
"price": 29.99,
}

respondWithSuccess(c, product)
})

router.Run(":8080")
}

Example 3: Authentication Status Codes

Here's how you might handle authentication with appropriate status codes:

go
package main

import (
"net/http"
"github.com/gin-gonic/gin"
)

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

// Login endpoint
router.POST("/login", func(c *gin.Context) {
var loginData struct {
Username string `json:"username"`
Password string `json:"password"`
}

if err := c.BindJSON(&loginData); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Invalid request body",
})
return
}

// Simple validation example (in real apps, use proper auth)
if loginData.Username == "" || loginData.Password == "" {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Username and password are required",
})
return
}

// Check credentials (simplified example)
if loginData.Username == "admin" && loginData.Password == "secret" {
c.JSON(http.StatusOK, gin.H{
"message": "Login successful",
"token": "sample-jwt-token",
})
} else {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "Invalid credentials",
})
}
})

// Protected endpoint
router.GET("/admin", func(c *gin.Context) {
token := c.GetHeader("Authorization")

if token == "" {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "Authentication required",
})
return
}

if token != "Bearer sample-jwt-token" {
c.JSON(http.StatusForbidden, gin.H{
"error": "Access denied",
})
return
}

c.JSON(http.StatusOK, gin.H{
"message": "Welcome to the admin panel",
})
})

router.Run(":8080")
}

Common Status Codes in Web APIs

Here's a reference for commonly used status codes in RESTful APIs:

CodeStatusDescriptionWhen to Use
200OKSuccessWhen request has succeeded
201CreatedResource createdAfter POST that creates a new resource
204No ContentSuccess with no contentDELETE operations or updates that return no content
400Bad RequestInvalid inputWhen request has invalid syntax
401UnauthorizedAuthentication requiredWhen authentication is missing or invalid
403ForbiddenPermission deniedWhen authenticated user lacks required permissions
404Not FoundResource not foundWhen requested resource doesn't exist
405Method Not AllowedHTTP method not allowedWhen HTTP method is not supported for the resource
409ConflictRequest conflicts with server stateWhen there's a conflict with resource state
422Unprocessable EntityValidation errorsWhen request is well-formed but has semantic errors
429Too Many RequestsRate limitedWhen user has sent too many requests
500Internal Server ErrorServer errorWhen an unexpected condition prevented request fulfillment
503Service UnavailableTemporary unavailableDuring maintenance or overloaded conditions

Best Practices for Using Status Codes

  1. Be consistent: Use the same status codes for similar situations throughout your API
  2. Don't create custom codes: Stick to standard HTTP status codes
  3. Include detailed error messages: Particularly with 4xx errors, include helpful error messages
  4. Log 5xx errors: Server errors should always be logged for investigation
  5. Don't expose sensitive information: Error messages shouldn't reveal implementation details
  6. Return appropriate content types: Match your response body with the Content-Type header

Summary

HTTP status codes are a crucial part of web API development with Gin. By using them correctly, you communicate clearly with API consumers about request outcomes. Status codes help clients understand whether requests succeeded or failed, and if they failed, what kind of error occurred.

Gin makes it easy to set appropriate status codes through methods like c.JSON(), c.XML(), or c.String() which all accept a status code as their first parameter. For clean, maintainable code, consider creating helper functions that standardize your API responses and error handling.

Additional Resources

Exercises

  1. Create a simple Gin API with endpoints that return different status codes based on conditions you define
  2. Implement a middleware that logs all non-200 status codes for debugging
  3. Build a REST API for a resource of your choice with proper status code handling for all CRUD operations
  4. Create custom response helper functions that standardize your API's error and success responses
  5. Implement rate limiting middleware that returns 429 Too Many Requests when appropriate


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)