PocketBase uses
echo (v5)
for routing. The internal router is exposed in the app.OnBeforeServe
event hook and you can register
your own custom endpoints and/or middlewares the same way as using directly echo.
Register new route
import (
"net/http"
"github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
)
...
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
// or you can also use the shorter e.Router.GET("/articles/:slug", handler, middlewares...)
e.Router.AddRoute(echo.Route{
Method: http.MethodGet,
Path: "/articles/:slug",
Handler: func(c echo.Context) error {
record, err := app.Dao().FindFirstRecordByData("articles", "slug", c.PathParam("slug"))
if err != nil {
return apis.NewNotFoundError("The article does not exist.", err)
}
// enable ?expand query param support
apis.EnrichRecord(c, app.Dao(), record)
return c.JSON(http.StatusOK, record)
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
},
})
return nil
})
To avoid collisions with future internal routes it is recommended to use a unique prefix when
adding routes to the /api base endpoint, eg. /api/yourapp/...
path.
Middlewares
In addition to the echo middlewares, you can also make use of some PocketBase specific ones:
apis.ActivityLogger(app)
apis.RequireGuestOnly()
apis.RequireRecordAuth(optCollectionNames)
apis.RequireSameContextRecordAuth()
apis.RequireAdminAuth()
apis.RequireAdminOrRecordAuth(optCollectionNames)
apis.RequireAdminOrOwnerAuth(ownerIdParam = "id")
You could also create your own middleware by returning echo.MiddlewareFunc
:
func myCustomMiddleware() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// eg. inspecting some header value before processing the request
someHeaderVal := c.Request().Header.Get("some-header")
...
return next(c)
}
}
}
Middlewares could be registed both to a single route or globally using
e.Router.Use(myCustomMiddleware())
.
Error response
PocketBase has a global error handler and every returned error
from a route or middleware
will be safely converted by default to a generic HTTP 400 error to avoid accidentally leaking sensitive
information (the original error will be visible only in the Admin UI > Logs or when in
--debug
mode).
To make it easier returning formatted json error responses, PocketBase provides
apis.ApiError
struct that implements the error
interface and can be instantiated
with the following factories:
// if message is empty string, a default one will be set
// data is optional and could be nil or the original error
apis.NewNotFoundError(message string, data any)
apis.NewBadRequestError(message string, data any)
apis.NewForbiddenError(message string, data any)
apis.NewUnauthorizedError(message string, data any)
apis.NewApiError(status int, message string, data any)
Get logged admin or app user
The current request authentication state can be accessed from the echo.Context
in a route handler,
middleware or request hook.
Get logged admin or app user in a route handler
import (
"github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/models"
)
...
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
e.Router.GET("/hello", func(c echo.Context) error {
// to get the authenticated admin:
admin, _ := c.Get(apis.ContextAdminKey).(*models.Admin)
if admin == nil {
return apis.NewForbiddenError("Only admins can access this endpoint", nil)
}
// or to get the authenticated record:
authRecord, _ := c.Get(apis.ContextAuthRecordKey).(*models.Record)
if authRecord == nil {
return apis.NewForbiddenError("Only auth records can access this endpoint", nil)
}
...
return c.String(200, "Hello world!")
})
return nil
})
Get logged admin or app user in a middleware
import (
"github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/models"
)
...
func myCustomMiddleware(app core.App) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// to get the authenticated admin:
admin, _ := c.Get(apis.ContextAdminKey).(*models.Admin)
if admin == nil {
return apis.NewForbiddenError("Only admins can access this endpoint", nil)
}
// or to get the authenticated record:
authRecord, _ := c.Get(apis.ContextAuthRecordKey).(*models.Record)
if authRecord == nil {
return apis.NewForbiddenError("Only auth records can access this endpoint", nil)
}
...
return next(c)
}
}
}
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
e.Router.Use(myCustomMiddleware(app))
return nil
})
Get logged admin or app user in a request hook
All request hooks (aka. those ending with *Request
) expose the current request context as
e.HttpContext
field.
import (
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/models"
)
...
app.OnRecordBeforeCreateRequest().Add(func(e *core.RecordCreateEvent) error {
// to get the authenticated admin:
admin, _ := e.HttpContext.Get(apis.ContextAdminKey).(*models.Admin)
if admin == nil {
return apis.NewForbiddenError("Only admins can access this endpoint", nil)
}
// or to get the authenticated record:
authRecord, _ := e.HttpContext.Get(apis.ContextAuthRecordKey).(*models.Record)
if authRecord == nil {
return apis.NewForbiddenError("Only auth records can access this endpoint", nil)
}
...
return nil
})
Sending request to custom routes using the SDKs
The official PocketBase SDKs expose the internal send()
method that could be used to send requests
to your custom endpoint(s).
import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');
await pb.send("/hello", {
// for all possible options check
// https://developer.mozilla.org/en-US/docs/Web/API/fetch#options
query: { "abc": 123 },
});
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('http://127.0.0.1:8090');
await pb.send("/hello", query: { "abc": 123 })