RESTful CRUD Example
This example demonstrates a complete RESTful CRUD API using sebuf, showcasing:
- All HTTP verbs: GET, POST, PUT, PATCH, DELETE
- Path parameters:
/products/{product_id} - Query parameters: Pagination, filtering, sorting, and search
- PUT vs PATCH semantics: Full replacement vs partial update
Features Demonstrated#
| Feature | Description |
|---|---|
HTTP_METHOD_GET |
List and retrieve operations |
HTTP_METHOD_POST |
Create operations with request body |
HTTP_METHOD_PUT |
Full resource replacement |
HTTP_METHOD_PATCH |
Partial resource update with optional fields |
HTTP_METHOD_DELETE |
Delete operations with confirmation header |
| Path parameters | {product_id} bound from URL |
| Query parameters | page, limit, category, min_price, max_price, sort, desc, q |
| Request validation | buf.validate rules for all fields |
| Header validation | X-API-Key required, X-Confirm-Delete for deletes |
Quick Start#
# Generate code and run the server
make demo
# Or step by step:
make generate
make runAPI Endpoints#
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/products |
List products with pagination/filtering |
| GET | /api/v1/products/{product_id} |
Get a single product |
| POST | /api/v1/products |
Create a new product |
| PUT | /api/v1/products/{product_id} |
Full update (replace all fields) |
| PATCH | /api/v1/products/{product_id} |
Partial update (only provided fields) |
| DELETE | /api/v1/products/{product_id} |
Delete a product |
Query Parameters#
The GET /api/v1/products endpoint supports:
| Parameter | Type | Description |
|---|---|---|
page |
int | Page number (default: 1) |
limit |
int | Items per page (default: 20, max: 100) |
category |
string | Filter by category ID |
min_price |
double | Minimum price filter |
max_price |
double | Maximum price filter |
sort |
string | Sort field (name, price, created_at) |
desc |
bool | Sort descending (default: false) |
q |
string | Search in name/description |
Example Requests#
List Products with Filtering#
curl -X GET "http://localhost:8080/api/v1/products?page=1&limit=10&category=cat-electronics&min_price=50&sort=price" \
-H "X-API-Key: 123e4567-e89b-12d3-a456-426614174000"Create a Product#
curl -X POST http://localhost:8080/api/v1/products \
-H "Content-Type: application/json" \
-H "X-API-Key: 123e4567-e89b-12d3-a456-426614174000" \
-d '{
"name": "Wireless Headphones",
"description": "Noise-canceling Bluetooth headphones",
"price": 99.99,
"stock_quantity": 100,
"category_id": "cat-electronics",
"tags": ["audio", "wireless"]
}'Full Update (PUT)#
curl -X PUT http://localhost:8080/api/v1/products/123e4567-e89b-12d3-a456-426614174001 \
-H "Content-Type: application/json" \
-H "X-API-Key: 123e4567-e89b-12d3-a456-426614174000" \
-d '{
"name": "Premium Wireless Headphones",
"description": "Updated premium headphones",
"price": 149.99,
"stock_quantity": 50,
"category_id": "cat-electronics",
"tags": ["audio", "wireless", "premium"]
}'Partial Update (PATCH)#
curl -X PATCH http://localhost:8080/api/v1/products/123e4567-e89b-12d3-a456-426614174001 \
-H "Content-Type: application/json" \
-H "X-API-Key: 123e4567-e89b-12d3-a456-426614174000" \
-d '{"price": 129.99}'Delete a Product#
curl -X DELETE http://localhost:8080/api/v1/products/123e4567-e89b-12d3-a456-426614174001 \
-H "X-API-Key: 123e4567-e89b-12d3-a456-426614174000" \
-H "X-Confirm-Delete: true"Generated Files#
After running make generate:
api/
proto/
models/
product.pb.go # Product message types
services/
product_service.pb.go # Service interface
product_service_http.pb.go # HTTP handler registration
product_service_http_binding.pb.go # Request/response binding
product_service_http_config.pb.go # Server options
product_service_http_mock.pb.go # Mock implementation
product_service_client.pb.go # HTTP client
docs/
ProductService.openapi.yaml # OpenAPI 3.1 spec (YAML)
ProductService.openapi.json # OpenAPI 3.1 spec (JSON)
Using the Generated Client#
The generated HTTP client provides type-safe access to your API. See client_example.go for a complete working example.
Basic Usage#
package main
import (
"context"
"net/http"
"time"
"github.com/SebastienMelki/sebuf/examples/restful-crud/api/proto/models"
"github.com/SebastienMelki/sebuf/examples/restful-crud/api/proto/services"
)
func main() {
// Create client with options
client := services.NewProductServiceClient(
"http://localhost:8080",
services.WithProductServiceAPIKey("your-api-key"),
services.WithProductServiceHTTPClient(&http.Client{
Timeout: 30 * time.Second,
}),
)
ctx := context.Background()
// Create a product
product, err := client.CreateProduct(ctx, &models.CreateProductRequest{
Name: "New Product",
Description: "A great product",
Price: 99.99,
CategoryId: "electronics",
Tags: []string{"new", "featured"},
})
// List products with query parameters
list, err := client.ListProducts(ctx, &models.ListProductsRequest{
Page: 1,
Limit: 10,
Category: "electronics",
SortBy: "price",
})
// Get a product by ID
product, err := client.GetProduct(ctx, &models.GetProductRequest{
ProductId: "product-123",
})
// Delete with confirmation header
_, err = client.DeleteProduct(ctx, &models.DeleteProductRequest{
ProductId: "product-123",
},
services.WithProductServiceCallConfirmDelete("true"),
)
}Client Features#
- Functional options pattern: Configure client behavior at creation time
- Per-call options: Customize individual requests with headers or content type
- JSON and Protobuf support: Use
ContentTypeJSONorContentTypeProto - Generated header helpers:
WithProductServiceAPIKey(),WithProductServiceCallConfirmDelete() - Automatic path/query parameter handling: URL building is automatic
Key Concepts#
PUT vs PATCH#
- PUT: Full replacement. All fields must be provided. Missing fields are set to defaults.
- PATCH: Partial update. Only provided fields are updated. Uses
optionalproto3 fields.
Path Parameters#
Path parameters like {product_id} are automatically bound from the URL path to the corresponding request field with the same name.
Query Parameters#
Fields annotated with (sebuf.http.query) are parsed from URL query string instead of request body. This is useful for GET requests that shouldn't have a body.
Client Examples#
This directory contains several example files demonstrating advanced client usage patterns:
| File | Description |
|---|---|
client_example.go |
Basic CRUD operations with the generated client |
client_error_handling.go |
Comprehensive error handling patterns |
client_content_types.go |
JSON vs binary protobuf content types |
client_per_call_options.go |
Per-request customization with call options |
Running the Examples#
First, start the server:
make runThen run any example:
go run client_example.go
go run client_error_handling.go
go run client_content_types.go
go run client_per_call_options.goError Handling Example#
Demonstrates handling different error types:
- Validation errors (HTTP 400) with field-level details
- Not found errors (HTTP 404)
- Missing required headers
- Network errors and timeouts
- Context cancellation
var validationErr *sebufhttp.ValidationError
if errors.As(err, &validationErr) {
for _, violation := range validationErr.GetViolations() {
fmt.Printf("Field '%s': %s\n", violation.GetField(), violation.GetDescription())
}
}Content Types Example#
Demonstrates switching between JSON and binary protobuf:
- Default JSON client
- Binary protobuf for better performance
- Per-request content type override
- Performance comparison
// Default JSON
client := services.NewProductServiceClient("http://localhost:8080")
// Binary protobuf (30-50% smaller payloads)
protoClient := services.NewProductServiceClient(
"http://localhost:8080",
services.WithProductServiceContentType(services.ContentTypeProto),
)
// Per-request override
client.ListProducts(ctx, req,
services.WithProductServiceCallContentType(services.ContentTypeProto),
)Per-Call Options Example#
Demonstrates request-level customization:
- Custom headers per request (tracing, correlation IDs)
- Generated header helpers
- Content type override per request
- Dynamic headers based on context
- A/B testing with headers
product, err := client.CreateProduct(ctx, req,
services.WithProductServiceHeader("X-Request-ID", "req-123"),
services.WithProductServiceHeader("X-Correlation-ID", "corr-456"),
services.WithProductServiceCallContentType(services.ContentTypeProto),
)