API Platform
Build JSON REST APIs with the Goose API platform.
Overview
The API platform serves JSON responses for building RESTful APIs.
Quick Start
package main
import (
"myapp/app"
"github.com/awesome-goose/goose"
"github.com/awesome-goose/goose/platforms/api"
)
func main() {
platform := api.NewPlatform(
api.WithHost("localhost"),
api.WithPort(8080),
)
module := &app.AppModule{}
stop, err := goose.Start(goose.API(platform, module, nil))
if err != nil {
panic(err)
}
defer stop()
}
Configuration
Platform Options
platform := api.NewPlatform(
api.WithHost("0.0.0.0"), // Listen address
api.WithPort(8080), // Port
api.WithTimeout(30), // Request timeout (seconds)
api.WithName("My API"), // API name
api.WithVersion("0.0.0"), // API version
api.WithAuthor("Your Name"), // Author
api.WithDescription("API description"),
)
Available Options
type Config struct {
Name string // API name
Version string // API version
Author string // Author
Description string // Description
Host string // Listen address
Port int // Port
Timeout int // Request timeout
}
Environment Configuration
platform := api.NewPlatform(
api.WithHost(env.String("HOST", "localhost")),
api.WithPort(env.Int("PORT", 8080)),
api.WithTimeout(env.Int("TIMEOUT", 30)),
)
Response Handling
JSON Responses
func (c *Controller) Index(ctx types.Context) any {
users := c.service.GetAll()
return users // Automatically serialized to JSON
}
func (c *Controller) Show(ctx types.Context) any {
user := c.service.GetByID(ctx.Request().Params()["id"])
if user == nil {
return map[string]any{
"error": "User not found",
"_status": 404,
}
}
return user
}
Custom Status Codes
func (c *Controller) Create(ctx types.Context) any {
user, err := c.service.Create(dto)
if err != nil {
return map[string]any{
"error": err.Error(),
"_status": 400,
}
}
return map[string]any{
"data": user,
"_status": 201,
}
}
Empty Response
func (c *Controller) Delete(ctx types.Context) any {
c.service.Delete(ctx.Request().Params()["id"])
return map[string]any{
"_status": 204,
}
}
Request Handling
Request Body
import "encoding/json"
type CreateUserDTO struct {
Email string `json:"email" validate:"required,email"`
Name string `json:"name" validate:"required"`
}
func (c *Controller) Create(ctx types.Context) any {
body, err := ctx.Request().Body()
if err != nil {
return map[string]any{
"error": "Failed to read body",
"_status": 400,
}
}
var dto CreateUserDTO
if err := json.Unmarshal(body, &dto); err != nil {
return map[string]any{
"error": "Invalid request body",
"_status": 400,
}
}
// Use dto...
}
Query Parameters
func (c *Controller) Index(ctx types.Context) any {
queries := ctx.Request().Queries()
page := queries["page"]
limit := queries["limit"]
sort := queries["sort"]
return c.service.GetPaginated(page, limit, sort)
}
Path Parameters
func (c *Controller) Show(ctx types.Context) any {
id := ctx.Request().Params()["id"]
return c.service.GetByID(id)
}
Middleware
Middleware implements the types.Middleware interface:
type Middleware interface {
Handle(ctx Context) error
}
Authentication
type AuthMiddleware struct {
authService *AuthService `inject:""`
}
func (m *AuthMiddleware) Handle(ctx types.Context) error {
headers := ctx.Request().Headers()
token := ""
if auth := headers["Authorization"]; len(auth) > 0 {
token = auth[0]
}
user, err := m.authService.ValidateToken(token)
if err != nil {
// Return error to stop the chain
return err
}
ctx.SetValue("user", user)
return nil // Continue to next middleware/handler
}
Apply Middleware
func (c *UserController) Routes() types.Routes {
return types.Routes{
{Method: "GET", Path: "/users", Handler: c.Index},
{Method: "GET", Path: "/users/:id", Handler: c.Show},
{Method: "POST", Path: "/users", Handler: c.Create,
Middlewares: []any{&AuthMiddleware{}}},
}
}
API Versioning
URL Path Versioning
// v1 module
type V1Module struct{}
func (m *V1Module) Declarations() []any {
return []any{
&v1.UsersController{},
}
}
// v2 module
type V2Module struct{}
func (m *V2Module) Declarations() []any {
return []any{
&v2.UsersController{},
}
}
// Register with prefixes
platform := api.NewPlatform(api.WithPort(8080))
goose.Start(
goose.API(platform, &V1Module{}, nil, api.WithPrefix("/api/v1")),
goose.API(platform, &V2Module{}, nil, api.WithPrefix("/api/v2")),
)
Header Versioning
type VersionMiddleware struct{}
func (m *VersionMiddleware) Handle(ctx types.Context) error {
headers := ctx.Request().Headers()
version := "v1"
if v := headers["API-Version"]; len(v) > 0 {
version = v[0]
}
ctx.SetValue("api_version", version)
return nil
}
Error Handling
Standard Error Response
type ErrorResponse struct {
Error string `json:"error"`
Code string `json:"code,omitempty"`
Details map[string]string `json:"details,omitempty"`
}
func (c *Controller) Create(ctx types.Context) any {
user, err := c.service.Create(dto)
if err != nil {
if validationErr, ok := err.(*ValidationError); ok {
return map[string]any{
"error": "Validation failed",
"code": "VALIDATION_ERROR",
"details": validationErr.Fields,
"_status": 400,
}
}
return map[string]any{
"error": "Internal server error",
"code": "INTERNAL_ERROR",
"_status": 500,
}
}
return map[string]any{
"data": user,
"_status": 201,
}
}
CORS
type CORSMiddleware struct{}
func (m *CORSMiddleware) Handle(ctx types.Context) error {
resp := ctx.Response()
resp.SetHeader("Access-Control-Allow-Origin", "*")
resp.SetHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
resp.SetHeader("Access-Control-Allow-Headers", "Content-Type, Authorization")
if ctx.Request().Method() == types.OPTIONS {
// Handle preflight request
resp.Write(types.SerialJSON, []byte(""), 204)
return nil
}
return nil
}
Best Practices
- Use consistent response format across all endpoints
- Version your API from the start
- Validate input thoroughly
- Handle errors with appropriate status codes
- Document your API with OpenAPI/Swagger
- Use authentication for sensitive endpoints
- Implement rate limiting for public APIs
Next Steps
- Routing - Route definitions
- Controllers - Request handling
- Authentication - Secure your API