Docs

User Registration Example

User Registration Example Build a public POST /register endpoint that creates a user safely. This example uses one custom route, one pre-hook, and one custom handler. It is intentionally small so you can copy the pattern into your own app. What You Build POST /api/register -> pre

User Registration Example

Build a public POST /register endpoint that creates a user safely.

This example uses one custom route, one pre-hook, and one custom handler. It is intentionally small so you can copy the pattern into your own app.

What You Build

POST /api/register
  -> pre-hook validates input
  -> handler hashes password
  -> handler creates user_definition row
  -> response returns safe user fields only

Use this when the default POST /user_definition route is too generic for public signup.

1. Create The Route

In the admin app, create a route:

Field Value
Path /register
Method POST
Handler Custom
Target table user_definition

Keep the route public only if this endpoint is meant for signup. Otherwise attach normal route permissions.

2. Add A Pre-Hook

Attach this pre-hook to POST /register.

It checks the body before the handler runs.

const { email, password } = @BODY

if (!email) @THROW400("Email is required")
if (!password) @THROW400("Password is required")
if (password.length < 8) @THROW400("Password must be at least 8 characters")

const existing = await #user_definition.find({
  filter: { email: { _eq: email } },
  fields: "id",
  limit: 1
})

if (existing.data[0]) @THROW409("Email already exists")

Why this belongs in a pre-hook:

  • It rejects invalid requests before the handler does work.
  • It keeps the handler focused on creation.
  • The same pattern works for tenant checks, quotas, or invite validation.

3. Add The Custom Handler

Attach this handler to POST /register.

const { email, password, name } = @BODY

const hashedPassword = await @HELPERS.$bcrypt.hash(password)

const result = await #user_definition.create({
  data: {
    email,
    password: hashedPassword,
    name: name || null,
    isActive: true
  }
})

const user = result.data[0]

return {
  id: user.id,
  email: user.email,
  name: user.name,
  isActive: user.isActive
}

The handler does not return password.

4. Test The Endpoint

curl -X POST "http://localhost:3000/api/register" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "password": "password123",
    "name": "Mai Tran"
  }'

Expected response shape:

{
  "id": 12,
  "email": "[email protected]",
  "name": "Mai Tran",
  "isActive": true
}

5. Optional: Trigger A Welcome Flow

If you want email or onboarding work, trigger a flow in a post-hook.

if (!@ERROR && @DATA?.id) {
  await @TRIGGER("welcome-user", {
    userId: @DATA.id,
    email: @DATA.email
  })
}

Use a flow when the work can happen after the response or may need retry/history.

Common Mistakes

Creating users from the browser with /user_definition

Use a dedicated /register route for public signup. It gives you one place to validate, hash, and return safe fields.

Returning the full user row

Never return password, reset tokens, OAuth secrets, or internal fields from a public registration endpoint.

Putting all logic in one handler

Use pre-hook for validation, handler for creation, and post-hook/flow for side effects.

Related Documentation