Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 24 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,10 @@ func main() {
o := okapi.Default()

o.Get("/", func(c okapi.Context) error {
return c.JSON(http.StatusOK, okapi.M{"message": "Welcome to Okapi!"})
})
return c.OK(okapi.M{"message": "Welcome to Okapi!"})
},
okapi.DocSummary("Welcome page"),
)

if err := o.Start(); err != nil {
panic(err)
Expand All @@ -108,7 +110,7 @@ Visit [`http://localhost:8080`](http://localhost:8080) to see the response:
{"message": "Welcome to Okapi!"}
```

Visit [`http://localhost:8080/docs`](http://localhost:8080/docs) to se the documentation
Visit [`http://localhost:8080/docs/`](http://localhost:8080/docs/) to se the documentation

---

Expand All @@ -132,7 +134,11 @@ Organize routes with nesting and middleware:
api := o.Group("/api")

v1 := api.Group("/v1")
v2 := api.Group("/v2")

v1.Get("/users", getUsers)
v2.Get("/users", getUsers)


admin := api.Group("/admin", adminMiddleware)
admin.Get("/dashboard", getDashboard)
Expand Down Expand Up @@ -196,7 +202,6 @@ type Book struct {
ID int `json:"id" param:"id" query:"id" form:"id"`
Name string `json:"name" xml:"name" form:"name" min:"4" max:"50" required:"true"`
Price int `json:"price" form:"price" required:"true"`

Logo *multipart.FileHeader `form:"logo" required:"true"`
Content string `header:"Content-Type" json:"content-type" xml:"content-type" required:"true"`
// Supports both ?tags=a&tags=b and ?tags=a,b
Expand All @@ -206,7 +211,7 @@ type Book struct {
o.Post("/books", func(c okapi.Context) error {
book := &Book{}
if err := c.Bind(book); err != nil {
return c.AbortBadRequest(err)
return c.ErrorBadRequest(err)
}
return c.JSON(http.StatusOK, book)
})
Expand Down Expand Up @@ -318,8 +323,8 @@ o.Post("/books", createBook,
okapi.DocTag("bookController"),
okapi.DocBearerAuth(), // Enable Bearer token authentication

// Request documentation
okapi.DocRequest(BookRequest{}),
// RequestBody documentation
okapi.RequestBody(BookRequest{}),

// Response documentation
okapi.DocResponse(BookResponse{}),
Expand Down Expand Up @@ -350,17 +355,17 @@ o.Get("/books/{id}", getBook,

### Available Documentation Options

| Method | Description |
|-------------------|--------------------------------------|
| `DocSummary()` | Short endpoint description |
| `DocTag()` | Groups related endpoints |
| `DocTags()` | Groups related endpoints |
| `DocBearerAuth()` | Enables Bearer token authentication |
| `DocRequest()` | Documents request body structure |
| `DocResponse()` | Documents response structure |
| `DocPathParam()` | Documents path parameters |
| `DocQueryParam()` | Documents query parameters |
| `DocHeader()` | Documents header parameters |
| Method | Description |
|--------------------|-------------------------------------|
| `DocSummary()` | Short endpoint description |
| `DocTag()` | Groups related endpoints |
| `DocTags()` | Groups related endpoints |
| `DocBearerAuth()` | Enables Bearer token authentication |
| `DocRequestBody()` | Documents request body structure |
| `DocResponse()` | Documents response structure |
| `DocPathParam()` | Documents path parameters |
| `DocQueryParam()` | Documents query parameters |
| `DocHeader()` | Documents header parameters |

### Swagger UI Preview

Expand Down Expand Up @@ -446,7 +451,7 @@ o.Static("/static", "public/assets")

// Configure a secondary HTTPS server listening on port 8443
// This creates both HTTP (8080) and HTTPS (8443) endpoints
o.With(okapi.WithTLSServer(":443", tls))
o.With(okapi.WithTLSServer(":8443", tls))

// Register application routes and handlers
o.Get("/", func(c okapi.Context) error {
Expand Down
145 changes: 6 additions & 139 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,11 @@ func (c *Context) JSON(code int, v any) error {
})
}

// OK writes a JSON response with 200 status code.
func (c *Context) OK(v any) error {
return c.JSON(http.StatusOK, v)
}

// XML writes an XML response with the given status code.
func (c *Context) XML(code int, v any) error {
return c.writeResponse(code, XML, func() error {
Expand Down Expand Up @@ -452,142 +457,4 @@ func (c *Context) ServeFileInline(path, filename string) {
http.ServeFile(c.Response, c.Request, path)
}

// ********** Error Handling *************

// Error writes an error response with the given status code and message.
// Returns an error if writing the response fails.
func (c *Context) Error(code int, message string) error {
c.Response.WriteHeader(code)
_, err := c.Response.Write([]byte(message))
if err != nil {
return fmt.Errorf("failed to write error response: %w", err)
}
return nil
}

// ErrorNotFound writes a 404 Not Found response.
// Returns an error if writing the response fails.
func (c *Context) ErrorNotFound(message string) error {
return c.Error(http.StatusNotFound, message)
}

// ErrorInternalServerError writes a 500 Internal Server Error response.
// Returns an error if writing the response fails.
func (c *Context) ErrorInternalServerError(message string) error {
return c.Error(http.StatusInternalServerError, message)
}

// ErrorBadRequest writes a 400 Bad Request response.
// Returns an error if writing the response fails.
func (c *Context) ErrorBadRequest(message string) error {
return c.Error(http.StatusBadRequest, message)
}

// ErrorUnauthorized writes a 401 Unauthorized response.
func (c *Context) ErrorUnauthorized(message string) error {
return c.Error(http.StatusUnauthorized, message)
}

// ErrorForbidden writes a 403 Forbidden response.
func (c *Context) ErrorForbidden(message string) error {
return c.Error(http.StatusForbidden, message)
}

// ErrorConflict writes a 409 Conflict response.
func (c *Context) ErrorConflict(message string) error {
return c.Error(http.StatusConflict, message)
}

// ErrorUnprocessableEntity writes a 422 Unprocessable Entity response.
func (c *Context) ErrorUnprocessableEntity(message string) error {
return c.Error(http.StatusUnprocessableEntity, message)
}

// ErrorTooManyRequests writes a 429 Too Many Requests response.
func (c *Context) ErrorTooManyRequests(message string) error {
return c.Error(http.StatusTooManyRequests, message)
}

// ErrorServiceUnavailable writes a 503 Service Unavailable response.
func (c *Context) ErrorServiceUnavailable(message string) error {
return c.Error(http.StatusServiceUnavailable, message)
}

// AbortWithError writes an error response with the given status code and standardized format.
// Returns the error for chaining.
func (c *Context) AbortWithError(code int, msg string, err error) error {
details := ""
if err != nil {
details = err.Error()
}

return c.JSON(code, ErrorResponse{
Code: code,
Message: msg,
Details: details,
})
}

// Abort writes an error response with 500 status code and standardized format.
func (c *Context) Abort(err error) error {
return c.AbortWithError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError), err)
}

// abortWithStatus is a helper for status-only abort functions
func (c *Context) abortWithStatus(code int, defaultMsg string, msg ...string) error {
message := defaultMsg
if len(msg) > 0 && msg[0] != "" {
message = msg[0]
}
return c.AbortWithError(code, message, nil)
}

// AbortBadRequest writes an error response with 400 status code and standardized format.
func (c *Context) AbortBadRequest(msg ...string) error {
return c.abortWithStatus(http.StatusBadRequest, http.StatusText(http.StatusBadRequest), msg...)
}

// AbortUnauthorized writes an error response with 401 status code.
func (c *Context) AbortUnauthorized(msg ...string) error {
return c.abortWithStatus(http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized), msg...)
}

// AbortForbidden writes an error response with 403 status code.
func (c *Context) AbortForbidden(msg ...string) error {
return c.abortWithStatus(http.StatusForbidden, http.StatusText(http.StatusForbidden), msg...)
}

// AbortNotFound writes an error response with 404 status code.
func (c *Context) AbortNotFound(msg ...string) error {
return c.abortWithStatus(http.StatusNotFound, http.StatusText(http.StatusNotFound), msg...)
}

// AbortConflict writes an error response with 409 status code.
func (c *Context) AbortConflict(msg ...string) error {
return c.abortWithStatus(http.StatusConflict, http.StatusText(http.StatusConflict), msg...)
}

// AbortValidationError writes an error response with 422 status code.
func (c *Context) AbortValidationError(msg ...string) error {
return c.abortWithStatus(http.StatusUnprocessableEntity, http.StatusText(http.StatusUnprocessableEntity), msg...)
}

// AbortTooManyRequests writes an error response with 429 status code.
func (c *Context) AbortTooManyRequests(msg ...string) error {
return c.abortWithStatus(http.StatusTooManyRequests, http.StatusText(http.StatusTooManyRequests), msg...)
}

// AbortWithStatus writes an error response with the given status code and message.
// Useful when you don't have an error object but just a message.
func (c *Context) AbortWithStatus(code int, message string) error {
return c.JSON(code, ErrorResponse{
Code: code,
Message: http.StatusText(code),
Details: message,
})
}

// AbortWithJSON writes a custom JSON error response with the given status code.
func (c *Context) AbortWithJSON(code int, jsonObj interface{}) error {
return c.JSON(code, jsonObj)
}
// ************ Errors in errors.go *****************
54 changes: 37 additions & 17 deletions doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,41 @@
* SOFTWARE.
*/

// Package okapi is a modern, minimalist HTTP web framework for Go, inspired by the simplicity of FastAPI.
//
// Designed to be intuitive, lightweight, and powerful, Okapi makes it easy to build fast and flexible
// web applications and REST APIs.
//
// Features:
//
// - Clean and expressive API design
// - Powerful binding from JSON, XML, forms, query, headers, and path parameters
// - Route grouping and middleware chaining
// - Built-in middleware: Basic Auth, JWT, OAuth
// - Easy custom middleware support
// - Cross-Origin Resource Sharing (CORS)
// - Templating engine integration
// - Static file serving
// - Built entirely on Go’s standard library
// - Simple and clear documentation
// Package okapi is a modern, minimalist HTTP web framework for Go,
// inspired by FastAPI's elegance. Designed for simplicity, performance,
// and developer happiness, it helps you build fast, scalable, and well-documented APIs
// with minimal boilerplate.
//
// The framework is named after the okapi (/oʊˈkɑːpiː/), a rare and graceful mammal
// native to the rainforests of the northeastern Democratic Republic of the Congo.
// Just like its namesake — which resembles a blend of giraffe and zebra — Okapi blends
// simplicity and strength in a unique, powerful package.
//
// Key Features:
//
// - Intuitive & Expressive API:
// Clean, declarative syntax for effortless route and middleware definition.
//
// - Automatic Request Binding:
// Seamlessly parse JSON, XML, form data, query params, headers, and path variables into structs.
//
// - Built-in Auth & Security:
// Native support for JWT, OAuth2, Basic Auth, and custom middleware.
//
// - Blazing Fast Routing:
// Optimized HTTP router with low overhead for high-performance applications.
//
// - First-Class Documentation:
// OpenAPI 3.0 & Swagger UI integrated out of the box—auto-generate API docs with minimal effort.
//
// - Modern Tooling:
// Route grouping, middleware chaining, static file serving, templating engine support,
// CORS management, fine-grained timeout controls.
//
// - Developer Experience:
// Minimal boilerplate, clear error handling, structured logging, and easy testing.
//
// Okapi is built for speed, simplicity, and real-world use—whether you're prototyping or running in production.
//
// For more information and documentation, visit: https://github.com/jkaninda/okapi
package okapi
41 changes: 0 additions & 41 deletions error.go

This file was deleted.

Loading