Hooks

View as MarkdownEdit on GitHub

Hooks are functions that execute before or after an operation on a resource.

Hooks are functions that execute before or after an operation on a resource.

A Hook can be a resource hook, database hook, or application hook, allowing you to add custom logic to your APIs and extend FastSchema's functionality.

Help us improve

The FastSchema hooks system is still in its early stages, and we're actively working on enhancing it. If you have feedback or suggestions, please let us know.

Most hooks have a similar signature:

// The context object that was passed to the action
context.Context

// Middleware is a function that can be used to add middleware to a resource
type Middleware func(c Context) error

type QueryOption struct {
	Schema     *schema.Schema `json:"schema"`
	Limit      uint           `json:"limit"`
	Offset     uint           `json:"offset"`
	Columns    []string       `json:"columns"`
	Order      []string       `json:"order"`
	Predicates []*Predicate   `json:"predicates"`
	Query      string         `json:"query"`
	Args       any            `json:"args"`
	// For count query
	Column string `json:"column"`
	Unique bool   `json:"unique"`
}

OnPreResolve

func (a *App) OnPreResolve(middlewares ...fs.Middleware)

The OnPreResolve hook is executed before the resource handler is called. It can be used to add custom logic before the resource handler is executed.

There are two possible behaviors for the OnPreResolve hook:

  • Return nil to continue with the normal flow.
  • Return an error to stop the execution and return the error to the client.

Example:

app, _ := fastschema.New(&fs.Config{
  SystemSchemas: []any{Tag{}, Blog{}},
})

app.OnPreResolve(func(ctx fs.Context) error {
  user := ctx.User()
  resource := ctx.Resource()

  if user == nil && resource.ID() == "api.user.me" {
    return errors.Unauthenticated("You are not authenticated")
  }

  return nil
})

app.Start()

OnPostResolve

func (a *App) OnPostResolve(middlewares ...fs.Middleware)

The OnPostResolve hook is executed after the resource handler is called. It can be used to add custom logic after the resource handler is executed or to manipulate the response before it is sent to the client.

Example:

app, _ := fastschema.New(&fs.Config{
  SystemSchemas: []any{Tag{}, Blog{}},
})

app.OnPostResolve(func(ctx fs.Context) error {
  // We only want to modify the response of the "api.hello" resource
  if ctx.Resource().ID() != "api.hello" {
    return nil
  }

  // If there was an error, we don't want to modify the response
  if ctx.Result().Error != nil {
    return nil
  }

  // Modify the response from "Hello World" to "Modified response"
  ctx.Result().Data = "Modified response"

  // Or set the result to a new value
  // ctx.Result(&fs.Result{ Data: "Modified response" })

  return nil
})

app.API().Add(fs.Get("hello", func(ctx fs.Context, _ any) (any, error) {
  return "Hello World", nil
}, &fs.Meta{Public: true}))

app.Start()

OnPreUserRegister

type RegistrationInput struct {
	Email      string         // signup email (mutable)
	Username   string         // signup username (mutable)
	Provider   string         // "local" or the OAuth provider name
	ProviderID string         // OAuth provider subject id (empty for local)
	Profile    map[string]any // raw provider profile (OAuth only)
	IsOAuth    bool           // true for social-login registration
}

type PreUserRegisterHook = func(
	ctx context.Context,
	in *RegistrationInput,
) error

func (a *App) OnPreUserRegister(hooks ...fs.PreUserRegisterHook)

The OnPreUserRegister hook is executed just before a self-service user is created. It fires for both local (email/password) registration and OAuth/social signup. It does not fire for admin-created users.

This is the extension point for custom signup rules such as invite-only gating, or blocking disposable-email / free-webmail domains.

There are two possible behaviors for the OnPreUserRegister hook:

  • Return nil to allow the registration to continue.
  • Return an error to reject the registration before the user row is created.

The hook receives a pointer to RegistrationInput and may mutate Email/Username (for example, to normalize them); the mutated values are applied to the persisted user.

Built-in registration policy

The opt-in Registration Policy (allow/block email domains, reserved usernames, email normalization) runs as the first OnPreUserRegister hook, so your custom hooks run afterwards on the already-normalized input.

Example:

app, _ := fastschema.New(&fs.Config{
  SystemSchemas: []any{Tag{}, Blog{}},
})

app.OnPreUserRegister(func(ctx context.Context, in *fs.RegistrationInput) error {
  // Invite-only: reject any email that has not been invited
  if !isInvited(in.Email) {
    return errors.BadRequest("Registration is invite-only")
  }

  // Normalize the username
  in.Username = strings.ToLower(in.Username)

  return nil
})

app.Start()

OnPreDBQuery

type PreDBQuery = func(
	ctx context.Context,
	option *QueryOption,
) error

func (a *App) OnPreDBQuery(hooks ...db.PreDBQuery)

The OnPreDBQuery hook is executed before the database Get, First, Only operations are performed.

It can be used to manipulate the query before it is executed.

OnPostDBQuery

type PostDBQuery = func(
	ctx context.Context,
	option *QueryOption,
	entities []*schema.Entity,
) ([]*schema.Entity, error)

func (a *App) OnPostDBQuery(hooks ...db.PostDBGet)

The OnPostDBQuery hook is executed after the database Get, First, Only operations is performed.

It can be used to manipulate the query result before it is returned to the client.

Example:

app, _ := fastschema.New(&fs.Config{
  SystemSchemas: []any{Tag{}, Blog{}},
})

app.OnPostDBQuery(func(
  ctx context.Context,
  query *db.QueryOption,
  entities []*schema.Entity,
) ([]*schema.Entity, error) {
  if query.Model.Schema().Name != "file" {
    return entities, nil
  }

  for _, entity := range entities {
    path := entity.GetString("path")

    if path != "" {
      entity.Set("url", app.Disk().URL(path))
    }
  }

  return entities, nil
})

app.Start()

PreDBCreate

type PreDBCreate = func(
	ctx context.Context,
	schema *schema.Schema,
	createData *schema.Entity,
) error

func (a *App) OnPreDBCreate(hooks ...db.PreDBCreate)

The PreDBCreate hook is executed before the database Create operation is performed.

It can be used to manipulate the data before it is created.

  • schema is the schema of the entity.
  • createData is the data used to create the entity.

PostDBCreate

type PostDBCreate = func(
	ctx context.Context,
	schema *schema.Schema,
	dataCreate *schema.Entity,
	id uint64,
) error

func (a *App) OnPostDBCreate(hooks ...db.PostDBCreate)

The PostDBCreate hook is executed after the database Create operation is performed.

It can be used to trigger additional actions after a new entity is created.

  • schema is the schema of the entity.
  • id is the ID of the created entity.
  • dataCreate is the data used to create the entity.

Example:

app, _ := fastschema.New(&fs.Config{
  SystemSchemas: []any{Tag{}, Blog{}},
})

app.OnPostDBCreate(func(
  ctx context.Context,
	schema *schema.Schema,
	dataCreate *schema.Entity,
	id uint64,
) error {
  if schema.Name != "file" {
    return nil
  }

  // some logic to handle the file entity
  // e.g. move the file to a different location
  // or optimize the file for faster access

  return nil
})

PreDBUpdate

type PreDBUpdate = func(
	ctx context.Context,
	schema *schema.Schema,
	predicates []*Predicate,
	updateData *schema.Entity,
) error

func (a *App) OnPreDBUpdate(hooks ...db.PreDBUpdate)

The PreDBUpdate hook is executed before the database Update operation is performed.

It can be used to manipulate the update data or the predicates before the update is executed.

  • schema is the schema of the entity.
  • predicates are the predicates used to filter the entities for the update.
  • updateData is the data used to update the entities.

PostDBUpdate

type PostDBUpdate = func(
	ctx context.Context,
	schema *schema.Schema,
	predicates []*Predicate,
	updateData *schema.Entity,
	originalEntities []*schema.Entity,
	affected int,
) error

func (a *App) OnPostDBUpdate(hooks ...db.PostDBUpdate)

The PostDBUpdate hook is executed after the database Update operation is performed.

It can be used to trigger additional actions after one or many entities are updated.

  • schema is the schema of the entity.
  • predicates are the predicates used to filter the entities for the update.
  • updateData is the data used to update the entities.
  • originalEntities are the entities before the update.
  • affected is the number of entities affected by the update.

Example:

app, _ := fastschema.New(&fs.Config{
  SystemSchemas: []any{Tag{}, Blog{}},
})

app.OnPostDBUpdate(func(
  ctx context.Context,
	schema *schema.Schema,
	predicates []*Predicate,
	updateData *schema.Entity,
	originalEntities []*schema.Entity,
	affected int,
) error {
  if schema.Name != "tag" {
    return nil
  }

  // some logic to handle the tag entity update
  // e.g. update the tag in a search index
  // or update the tag in a cache

  return nil
})

PreDBDelete

type PreDBDelete = func(
	ctx context.Context,
	schema *schema.Schema,
	predicates []*Predicate,
) error

func (a *App) OnPreDBDelete(hooks ...db.PreDBDelete)

The PreDBDelete hook is executed before the database Delete operation is performed.

It can be used to manipulate the predicates before the delete is executed.

  • schema is the schema of the entity.
  • predicates are the predicates used to filter the entities for the delete.

PostDBDelete

type PostDBDelete = func(
  ctx context.Context,
	schema *schema.Schema,
	predicates []*Predicate,
	originalEntities []*schema.Entity,
	affected int,
) error

func (a *App) OnPostDBDelete(hooks ...db.PostDBDelete)

The PostDBDelete hook is executed after the database Delete operation is performed.

It can be used to trigger additional actions after one or many entities are deleted.

  • schema is the schema of the entity.
  • predicates are the predicates used to filter the entities for the delete.
  • originalEntities are the entities before the delete.
  • affected is the number of entities affected by the delete.

Example:

app, _ := fastschema.New(&fs.Config{
  SystemSchemas: []any{Tag{}, Blog{}},
})

app.OnPostDBDelete(func(
  ctx context.Context,
	schema *schema.Schema,
	predicates []*Predicate,
	originalEntities []*schema.Entity,
	affected int,
) error {
  if schema.Name != "tag" {
    return nil
  }

  // some logic to handle the tag entity delete
  // e.g. remove the tag from a search index
  // or remove the tag from a cache

  return nil
})

PreDBExec

type PreDBExec = func(
	ctx context.Context,
	option *QueryOption,
) error

func (a *App) OnPreDBExec(hooks ...db.PreDBExec)

The PreDBExec hook is executed before the database Exec operation is performed.

It can be used to manipulate the query before it is executed.

PostDBExec

type PostDBExec = func(
	ctx context.Context,
	option *QueryOption,
	result sql.Result,
) error

func (a *App) OnPostDBExec(hooks ...db.PostDBExec)

The PostDBExec hook is executed after the database Exec operation is performed.

It can be used to trigger additional actions after the query is executed.

  • result is the result of the query execution.