Routes

Routes are the HTTP endpoints your Grove application exposes. Each route is a file named route.grove placed inside your app's api/ directory. The file's location in the directory tree determines the URL path — there is no inline path string.

Prerequisites: Concepts Overview, Activities for data block familiarity. What you'll learn: How to create route files, handle HTTP methods, use named handlers, access request input, and work with data blocks inside routes.

Route File Convention

Routes use filesystem-based routing. Create a file named route.grove inside apps/<app>/api/ and the directory structure becomes the URL path:

File pathURL
apps/main/api/todo/route.grove/api/todo
apps/main/api/todo/[id]/route.grove/api/todo/:id
apps/main/api/organizations/route.grove/api/organizations
apps/main/api/organizations/[id]/route.grove/api/organizations/:id

Dynamic segments use bracket syntax: a directory named [id] captures that portion of the URL as a path parameter accessible via input.params.id.

Route Block Syntax

Every route.grove file contains a single route { } block with no path argument. The path comes entirely from the file's location:

route {
  GET {
    // handle GET requests
  }

  POST {
    // handle POST requests
  }
}

HTTP Method Handlers

Place one or more HTTP method blocks inside the route. Each method handler is independent and has its own data blocks:

route {
  GET {
    data result {
      query todo.list_todos {
        limit: input.query.limit
      }
    }
  }

  POST {
    data result {
      invoke todo.create_todo {
        title: input.body.title
      }
    }
  }
}

Named vs Unnamed Handlers

When a route needs only one handler per HTTP method, use an unnamed handler:

POST {
  data result {
    invoke todo.create_todo { title: input.body.title }
  }
}

When a route needs multiple handlers for the same HTTP method, give each handler a name:

POST create {
  data result {
    invoke organization.create_organization { name: input.body.name }
  }
}

POST archive {
  data result {
    invoke organization.archive_organization { id: input.body.id }
  }
}

Named handlers let you expose multiple operations under the same method on a single route.

The Input Object

Inside a route handler, the input object provides access to the incoming request:

FieldDescription
input.body.*The parsed request body (POST requests)
input.params.*Path parameters extracted from [segment] directories
input.query.*Query string parameters

The ctx object provides additional context:

FieldDescription
ctx.principalThe authenticated user (null if anonymous -- see Identity Flow)

Data Blocks

Route handlers use data blocks to perform operations. Each data block gives its result a name so you can reference it later:

data result {
  invoke module.action {
    field: value
  }
}
data records {
  query module.query {
    limit: input.query.limit
  }
}

Available operations inside route data blocks:

OperationPurpose
invokeCall an activity to perform a side effect
queryExecute a database query
fetchRetrieve a single record by identifier

Complete Example: Collection Route

A collection route handles listing and creating resources. Place this at apps/main/api/organizations/route.grove to serve /api/organizations:

route {
  GET {
    data result {
      query organization.list_organizations {
        limit: input.query.limit,
        offset: input.query.offset
      }
    }
  }

  POST {
    data result {
      invoke organization.create_organization {
        name: input.body.name
      }
    }
  }
}

Complete Example: Record Route

A record route handles operations on a single resource. Place this at apps/main/api/organizations/[id]/route.grove to serve /api/organizations/:id:

route {
  GET {
    data result {
      fetch organization { id: input.params.id }
    }
  }

  POST update {
    data result {
      invoke organization.update_organization {
        id: input.params.id,
        name: input.body.name
      }
    }
  }

  POST delete {
    data result {
      invoke organization.delete_organization { id: input.params.id }
    }
  }
}

Notice how the record route uses named POST handlers (update and delete) to expose multiple operations under the same HTTP method.

See Also