Echo HTTP Methods
Introduction
HTTP methods are fundamental to RESTful API design, defining the type of action to be performed on a specific resource. In the Echo framework, you can easily map different HTTP methods to handler functions, creating a clean and intuitive API structure. This guide will help you understand how to implement various HTTP methods in your Echo applications.
Understanding HTTP Methods
HTTP methods (also called verbs) tell the server what action to perform on a resource. The most commonly used HTTP methods are:
- GET: Retrieve data
- POST: Create new resources
- PUT: Update existing resources (complete replacement)
- PATCH: Partially update resources
- DELETE: Remove resources
- HEAD: Similar to GET but returns only headers, no body
- OPTIONS: Returns supported HTTP methods for a URL
Basic Method Routing in Echo
Echo provides method-specific functions to register routes for different HTTP methods. Here's how you can implement basic routes for common HTTP methods:
package main
import (
"net/http"
"github.com/labstack/echo/v4"
)
func main() {
e := echo.New()
// GET method route
e.GET("/users", getUsers)
// POST method route
e.POST("/users", createUser)
// PUT method route
e.PUT("/users/:id", updateUser)
// DELETE method route
e.DELETE("/users/:id", deleteUser)
e.Logger.Fatal(e.Start(":1323"))
}
// Handler functions
func getUsers(c echo.Context) error {
return c.String(http.StatusOK, "Get all users")
}
func createUser(c echo.Context) error {
return c.String(http.StatusCreated, "Create a user")
}
func updateUser(c echo.Context) error {
id := c.Param("id")
return c.String(http.StatusOK, "Update user with ID: "+id)
}
func deleteUser(c echo.Context) error {
id := c.Param("id")
return c.String(http.StatusOK, "Delete user with ID: "+id)
}
Working with Request Bodies
When handling POST, PUT, or PATCH requests, you'll often need to process the request body. Echo makes this easy with built-in binding functions:
package main
import (
"net/http"
"github.com/labstack/echo/v4"
)
// User represents our user data structure
type User struct {
ID string `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
e := echo.New()
e.POST("/users", createUserWithData)
e.Logger.Fatal(e.Start(":1323"))
}
func createUserWithData(c echo.Context) error {
user := new(User)
// Bind the request body to the user struct
if err := c.Bind(user); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
// Process the user data (in a real app, you'd save to a database)
// Return the created user with a 201 Created status
return c.JSON(http.StatusCreated, user)
}
Example Input
To test this endpoint, you can send a POST request with this JSON body:
{
"name": "Jane Doe",
"age": 25
}
Example Output
The server will respond with:
{
"id": "",
"name": "Jane Doe",
"age": 25
}
HTTP Method Groups
If you have multiple routes that share the same HTTP method and base path, you can use Echo's group functionality to make your code more organized:
package main
import (
"net/http"
"github.com/labstack/echo/v4"
)
func main() {
e := echo.New()
// Create a group for API v1
v1 := e.Group("/api/v1")
// GET routes
v1.GET("/users", getAllUsers)
v1.GET("/users/:id", getUserByID)
// POST routes
v1.POST("/users", createUser)
v1.POST("/users/:id/profile", updateUserProfile)
e.Logger.Fatal(e.Start(":1323"))
}
func getAllUsers(c echo.Context) error {
return c.String(http.StatusOK, "Get all users")
}
func getUserByID(c echo.Context) error {
id := c.Param("id")
return c.String(http.StatusOK, "Get user with ID: "+id)
}
func createUser(c echo.Context) error {
return c.String(http.StatusCreated, "Create a user")
}
func updateUserProfile(c echo.Context) error {
id := c.Param("id")
return c.String(http.StatusOK, "Update profile for user with ID: "+id)
}
Using the MATCH Method for Custom HTTP Methods
If you need to use a less common HTTP method or want to handle multiple methods with the same handler, Echo provides the MATCH
function:
package main
import (
"net/http"
"github.com/labstack/echo/v4"
)
func main() {
e := echo.New()
// Handle both GET and POST requests with the same handler
e.MATCH([]string{"GET", "POST"}, "/flexible-endpoint", handleMultipleMethods)
// Handle custom or less common HTTP method
e.MATCH([]string{"PATCH"}, "/users/:id", partiallyUpdateUser)
e.Logger.Fatal(e.Start(":1323"))
}
func handleMultipleMethods(c echo.Context) error {
method := c.Request().Method
return c.String(http.StatusOK, "Handled "+method+" request")
}
func partiallyUpdateUser(c echo.Context) error {
id := c.Param("id")
return c.String(http.StatusOK, "Partially updated user with ID: "+id)
}
The ANY Method - Handling All HTTP Methods
Sometimes you might want a route to respond to any HTTP method. Echo provides the ANY
method for this purpose:
package main
import (
"net/http"
"github.com/labstack/echo/v4"
)
func main() {
e := echo.New()
// This handler will respond to any HTTP method
e.Any("/ping", pingHandler)
e.Logger.Fatal(e.Start(":1323"))
}
func pingHandler(c echo.Context) error {
method := c.Request().Method
return c.String(http.StatusOK, "Received "+method+" request. Pong!")
}
Building a RESTful CRUD API Example
Let's put everything together to build a simple in-memory CRUD API for managing a collection of books:
package main
import (
"net/http"
"strconv"
"github.com/labstack/echo/v4"
)
// Book represents book data
type Book struct {
ID int `json:"id"`
Title string `json:"title"`
Author string `json:"author"`
Year int `json:"year"`
}
// In-memory database
var books = []Book{
{ID: 1, Title: "The Go Programming Language", Author: "Alan Donovan & Brian Kernighan", Year: 2015},
{ID: 2, Title: "Clean Code", Author: "Robert Martin", Year: 2008},
}
func main() {
e := echo.New()
// Book routes
e.GET("/books", getAllBooks)
e.GET("/books/:id", getBookByID)
e.POST("/books", createBook)
e.PUT("/books/:id", updateBook)
e.DELETE("/books/:id", deleteBook)
e.Logger.Fatal(e.Start(":1323"))
}
// Handler to get all books
func getAllBooks(c echo.Context) error {
return c.JSON(http.StatusOK, books)
}
// Handler to get a specific book by ID
func getBookByID(c echo.Context) error {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid book ID")
}
for _, book := range books {
if book.ID == id {
return c.JSON(http.StatusOK, book)
}
}
return echo.NewHTTPError(http.StatusNotFound, "Book not found")
}
// Handler to create a new book
func createBook(c echo.Context) error {
book := new(Book)
if err := c.Bind(book); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
// Set a new ID (in a real app, this would be handled by the database)
book.ID = len(books) + 1
books = append(books, *book)
return c.JSON(http.StatusCreated, book)
}
// Handler to update an existing book
func updateBook(c echo.Context) error {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid book ID")
}
book := new(Book)
if err := c.Bind(book); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
for i, b := range books {
if b.ID == id {
book.ID = id // Ensure ID remains the same
books[i] = *book
return c.JSON(http.StatusOK, book)
}
}
return echo.NewHTTPError(http.StatusNotFound, "Book not found")
}
// Handler to delete a book
func deleteBook(c echo.Context) error {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid book ID")
}
for i, book := range books {
if book.ID == id {
// Remove the book using index
books = append(books[:i], books[i+1:]...)
return c.NoContent(http.StatusNoContent)
}
}
return echo.NewHTTPError(http.StatusNotFound, "Book not found")
}
Testing Your RESTful API
You can test this API using tools like curl, Postman, or any API testing tool:
GET all books
curl http://localhost:1323/books
GET a specific book
curl http://localhost:1323/books/1
POST a new book
curl -X POST http://localhost:1323/books \
-H "Content-Type: application/json" \
-d '{"title":"Designing Data-Intensive Applications","author":"Martin Kleppmann","year":2017}'
PUT (update) a book
curl -X PUT http://localhost:1323/books/3 \
-H "Content-Type: application/json" \
-d '{"title":"Designing Data-Intensive Applications","author":"Martin Kleppmann","year":2018}'
DELETE a book
curl -X DELETE http://localhost:1323/books/3
Best Practices for HTTP Method Usage
When designing RESTful APIs with Echo, follow these best practices:
-
Use appropriate HTTP methods:
- GET for retrieving data
- POST for creating resources
- PUT for complete updates
- PATCH for partial updates
- DELETE for removing resources
-
Return proper status codes:
- 200 OK for successful GET, PUT, PATCH
- 201 Created for successful POST
- 204 No Content for successful DELETE
- 400 Bad Request for client errors
- 404 Not Found when resources don't exist
- 500 Internal Server Error for server issues
-
Make endpoints resource-focused, not action-focused:
- Good:
POST /users
(creates a user) - Avoid:
GET /createUser
(uses wrong HTTP method)
- Good:
-
Keep routes consistent and predictable:
- Collection:
/resources
(e.g.,/books
) - Single item:
/resources/:id
(e.g.,/books/1
) - Related items:
/resources/:id/sub-resources
(e.g.,/books/1/reviews
)
- Collection:
-
Handle errors gracefully with appropriate messages and status codes
Summary
Echo makes it easy to work with HTTP methods through its intuitive routing system. In this guide, we've covered:
- Basic method routing with Echo's method-specific functions
- Processing request bodies for methods like POST and PUT
- Organizing routes using method groups
- Handling custom or multiple methods with MATCH
- Responding to all methods with ANY
- Building a complete RESTful API with all CRUD operations
By understanding and correctly implementing HTTP methods in your Echo applications, you can create clean, intuitive, and standards-compliant APIs.
Additional Resources
- Echo Framework Documentation
- RESTful API Design Best Practices
- HTTP Methods - MDN Web Docs
- HTTP Status Codes
Exercises
To solidify your understanding of Echo HTTP methods, try these exercises:
- Create an API for managing a todo list with endpoints for listing, creating, updating, and deleting tasks.
- Extend the book API example to include searching books by title or author using query parameters.
- Implement a PATCH method for the book API that allows updating only specific fields of a book.
- Add validation to ensure that required fields are provided when creating or updating resources.
- Implement proper error handling for all endpoints to return appropriate status codes and error messages.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)