JSON Responses
Sending a JSON response
Section titled “Sending a JSON response”In Express you call res.status(200).json(payload). In Echo the equivalent is c.JSON(statusCode, payload). Echo sets the Content-Type: application/json header automatically.
// Expressapp.get('/books/:id', (req, res) => { const book = { id: req.params.id, title: 'Go in Action' }; res.status(200).json(book);});
// 404res.status(404).json({ message: 'not found' });// Echofunc getBook(c echo.Context) error { book := map[string]string{ "id": c.Param("id"), "title": "Go in Action", } return c.JSON(http.StatusOK, book)}
// 404 (via HTTPError — covered in Error Handling)return echo.NewHTTPError(http.StatusNotFound, "not found")Typed response structs
Section titled “Typed response structs”Using map[string]string is convenient for examples but production code should use typed response structs so the compiler catches field-name typos and the API surface is documented in code.
// TypeScript — response typeinterface BookResponse { id: string; title: string; author: string; publishedAt: string; // ISO 8601}
const toResponse = (b: Book): BookResponse => ({ id: b.id, title: b.title, author: b.author, publishedAt: b.createdAt.toISOString(),});
res.json(toResponse(book));// Go — response structtype BookResponse struct { ID string `json:"id"` Title string `json:"title"` Author string `json:"author"` PublishedAt string `json:"publishedAt"` // RFC 3339 / ISO 8601}
func toBookResponse(b Book) BookResponse { return BookResponse{ ID: b.ID, Title: b.Title, Author: b.Author, PublishedAt: b.CreatedAt.UTC().Format(time.RFC3339), }}
return c.JSON(http.StatusOK, toBookResponse(book))Standard HTTP status codes
Section titled “Standard HTTP status codes”Echo re-exports Go’s net/http constants. Use named constants instead of bare integers.
http.StatusOK // 200http.StatusCreated // 201http.StatusNoContent // 204http.StatusBadRequest // 400http.StatusUnauthorized // 401http.StatusForbidden // 403http.StatusNotFound // 404http.StatusConflict // 409http.StatusUnprocessableEntity // 422http.StatusInternalServerError // 500Try it — JSON marshal/unmarshal
Section titled “Try it — JSON marshal/unmarshal”encoding/json is the engine behind c.JSON. Explore it directly:
package main
import ( "encoding/json" "fmt" "time")
type BookResponse struct { ID string `json:"id"` Title string `json:"title"` Author string `json:"author"` PublishedAt time.Time `json:"publishedAt"`}
func main() { book := BookResponse{ ID: "abc-123", Title: "The Go Programming Language", Author: "Donovan & Kernighan", PublishedAt: time.Date(2015, 10, 26, 0, 0, 0, 0, time.UTC), }
// Marshal to JSON (what c.JSON does internally) data, err := json.MarshalIndent(book, "", " ") if err != nil { fmt.Println("marshal error:", err) return } fmt.Println(string(data))
// Unmarshal back var decoded BookResponse if err := json.Unmarshal(data, &decoded); err != nil { fmt.Println("unmarshal error:", err) return } fmt.Printf("\nDecoded title: %s\n", decoded.Title) fmt.Printf("Published: %s\n", decoded.PublishedAt.Format(time.RFC3339))}Loading Go runtime (first run only, ~8 MB)…
List responses and pagination
Section titled “List responses and pagination”Never return a raw JSON array at the top level — wrap it in an object so you can add metadata later without a breaking change.
// Express — paginated listres.json({ data: books, total: count, page: Number(req.query.page ?? 1), limit: Number(req.query.limit ?? 10),});// Echo — paginated listtype PaginatedBooks struct { Data []BookResponse `json:"data"` Total int `json:"total"` Page int `json:"page"` Limit int `json:"limit"`}
return c.JSON(http.StatusOK, PaginatedBooks{ Data: responses, Total: total, Page: page, Limit: limit,})