Server

The Grove server (grove-server) is an HTTP server that exposes your Grove actions as API endpoints, handles webhook routing, and serves query results. It supports three deployment modes -- module, app, and project -- depending on whether you are serving a single module or an entire project.

Prerequisites: CLI Reference for command-line basics, Database Backends for connection configuration. What you'll learn: How to start and configure the Grove server, how actions map to HTTP endpoints, and how route declarations are handled.


Hosted vs self-run

For Manzano-hosted deployments, you do not run the server yourself — manzano deploy packages your project and Manzano runs grove-server on your behalf. See the CLI deploy section.

The rest of this page covers the self-run path: running grove-server directly against your own database and infrastructure. This is the right path for local integration testing, CI environments, and on-premise or self-hosted deployments.

For rapid local iteration without a full server setup, prefer manzano dev — it starts a development server with an in-memory database and hot reloading.

Starting the Server

grove-server --module <dir> --listen 127.0.0.1:3000

Deployment Modes

Module Mode

Serves a single module. All actions are exposed under the module's namespace.

grove-server --module ./order --listen 127.0.0.1:3000

Actions are available at:

POST /api/order:create_order
POST /api/order:cancel_order
GET  /api/order:get_order?order_id=ord-001

App Mode

Serves multiple modules from a parent directory. Each module is automatically discovered and mounted.

grove-server --app ./my-project --listen 127.0.0.1:3000

Given a project structure:

my-project/
  order/
    actions.grove
  customer/
    actions.grove

Actions are available at:

POST /api/order:create_order
POST /api/customer:create_customer

Project Mode

Serves an entire project defined by a grove.toml configuration file. This is the recommended mode for production deployments.

grove-server --project ./my-project --listen 127.0.0.1:3000

Project mode reads grove.toml for module discovery, database configuration, and other settings. See Configuration Reference.

Listen Configuration

The --listen flag specifies the address and port the server binds to.

# Listen on localhost only
grove-server --module ./order --listen 127.0.0.1:3000

# Listen on all interfaces
grove-server --module ./order --listen 0.0.0.0:3000

# Listen on a specific port
grove-server --module ./order --listen 127.0.0.1:8080

Environment Variable

The listen address can also be set via the GROVE_LISTEN environment variable:

export GROVE_LISTEN="0.0.0.0:3000"
grove-server --module ./order

The --listen flag takes precedence over the environment variable.

Action Endpoints

Every action in a module is automatically exposed as an HTTP endpoint.

Endpoint Format

POST /api/<module>:<action>

Request Format

Action parameters are passed as a JSON object in the request body:

curl -X POST http://localhost:3000/api/order:create_order \
  -H "Content-Type: application/json" \
  -d '{
    "customer_id": "cust-001",
    "items": [
      {"product_id": "prod-001", "quantity": 2, "price": 19.99}
    ]
  }'

Response Format

Successful responses return the action's return value as JSON:

{
  "ok": true,
  "data": {
    "order_id": "ord-abc123"
  }
}

Error responses include the error message:

{
  "ok": false,
  "error": "Order total must be positive"
}

HTTP Status Codes

ScenarioStatus Code
Action succeeds200
Validation failure (fail statement)400
Authorization failure401
Action not found404
Method not allowed (non-POST to action endpoint)405
Internal error500

Route Handling

route declarations provide custom HTTP method and path mappings for actions.

route POST "/orders" => create_order
route GET "/orders/:id" => get_order
route DELETE "/orders/:id" => cancel_order
route PUT "/orders/:id/ship" => ship_order

Path Parameters

Path parameters (:param) are extracted from the URL and merged into the action's request parameters:

route GET "/orders/:order_id" => get_order

// Equivalent to calling get_order with { "order_id": "<value-from-url>" }
curl http://localhost:3000/orders/ord-001
# Calls get_order(order_id: "ord-001")

Route Priority

Routes are matched in declaration order. More specific routes should appear before less specific ones:

route GET "/orders/pending" => list_pending_orders  // matches first
route GET "/orders/:id" => get_order                // matches second

Route vs. Action Endpoints

When a route declaration exists, both the route path and the default action endpoint are available:

route POST "/orders" => create_order

This makes the action available at both:

Query Endpoints

Query declarations are exposed as GET endpoints:

GET /api/<module>:<query>?<params>
curl "http://localhost:3000/api/order:get_order?order_id=ord-001"

Query parameters are passed as URL query string parameters and are automatically type-coerced.

Webhook Endpoints

Webhook declarations register endpoints at their configured paths:

webhook stripe_events {
  provider: "stripe"
  secret: credentials.stripe_webhook_secret
  path: "/webhooks/stripe"
  handler: handle_stripe_event
}

This registers POST /webhooks/stripe. See Webhook Security for signature verification details.

Server Flags

FlagDescription
--module <dir>Serve a single module (module mode)
--app <dir>Serve all modules in a directory (app mode)
--project <dir>Serve a project defined by grove.toml (project mode)
--listen <addr:port>Address and port to bind to
--backend <backend>Database backend: sqlite, postgres, mysql, tidb
--db <connection>Database connection string
--cors-origin <origin>Allowed CORS origin (default: none)
--read-timeout <ms>Request read timeout in milliseconds (default: 30000)
--write-timeout <ms>Response write timeout in milliseconds (default: 30000)
--max-body-size <bytes>Maximum request body size (default: 1048576, 1 MB)
--workers <n>Number of worker threads (default: number of CPU cores)
--log-level <level>Log level: error, warn, info, debug, trace

Health Check

The server exposes a health check endpoint at:

GET /health

Response:

{
  "status": "ok",
  "version": "0.1.0",
  "uptime_seconds": 3600
}

Graceful Shutdown

The server handles SIGTERM and SIGINT signals for graceful shutdown:

  1. Stop accepting new connections.
  2. Wait for in-flight requests to complete (up to 30 seconds).
  3. Shut down worker threads.
  4. Close database connections.
  5. Exit with code 0.

See Also