API Routes in Apps

Each app can expose API endpoints by placing route.grove files inside its api/ directory. API routes follow the same file-based routing conventions as pages but use Grove's route block syntax instead of HTML templates.

Prerequisites: File-Based Routing, Routes. What you'll learn: How API routes fit into the app directory structure, how they differ from page routes, and how to organize them.

Directory Convention

API routes live under apps/<app>/api/. Each route.grove file's directory path determines its URL:

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

API Routes vs Page Routes

Page routes and API routes serve different purposes and use different file types:

AspectPage routesAPI routes
Filepage.html / index.htmlroute.grove
LocationApp root directoriesapi/ subdirectory
LanguageJQT templatesGrove
ResponseRendered HTML (or JSON via content negotiation)JSON
Data loadingpage.jq alongside the templatedata blocks inside the route
LayoutsWrapped by layout.htmlNo layout wrapping

Route Block Syntax

Each route.grove file contains a single route { } block. The URL path comes from the file's location -- there is no inline path string:

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

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

Dynamic Segments

Directory names with brackets capture path parameters, just like page routes:

api/todo/[id]/route.grove

Access parameters via input.params:

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

Example: Task Tracker Routes

A task tracker app might organize its API routes as:

apps/main/api/
  todo/
    route.grove          # GET /api/todo (list), POST /api/todo (create)
    [id]/
      route.grove        # GET /api/todo/:id (fetch), POST /api/todo/:id (update, delete)

Collection route at api/todo/route.grove:

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

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

Record route at api/todo/[id]/route.grove:

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

  POST update {
    data result {
      invoke todo.update_todo {
        id: input.params.id,
        title: input.body.title
      }
    }
  }

  POST delete {
    data result {
      invoke todo.delete_todo { id: input.params.id }
    }
  }
}

See Also