Alpha ZealPHP is early-stage and under active development. APIs may change between minor versions until v1.0. Feedback and bug reports welcome on GitHub.

Routes & APIs

Four routing patterns. Pick the one that fits your use case.

You will learn

  • Four ways to register URLs: implicit public, ZealAPI, explicit routes, namespaced
  • Dynamic path parameters like /users/{id}
  • How ZealPHP injects parameters by name via reflection
  • Return value conventions: int, array, string, Generator

The problem

So far, you've used implicit routing: drop a file in public/, get a URL. That covers pages. You've also used ZealAPI files in api/ for REST endpoints. But what about URLs like /users/42? Or restricting a route to POST only? Or versioning an API?

ZealPHP has four routing patterns. You've already used two — here are all four.

1. Implicit public routes

You learned this in Lesson 3. Files in public/ become URLs automatically:

public/index.php       → GET /
public/about.php       → GET /about
public/blog/post.php   → GET /blog/post

Use for: Pages the user visits. Simple, zero config.

2. Implicit API routes (ZealAPI)

You used this in Lesson 8. Files in api/ become REST endpoints:

// api/learn/notes.php → GET/POST /api/learn/notes
$notes = function () {
    $u = Auth::currentUser();
    // ... handle GET (list) and POST (create)
};

The closure variable name ($notes) must match the filename. Inside, $this is the ZealAPI instance with helpers like response(), json().

Use for: REST endpoints. One file per resource, auto-routed.

3. Explicit routes

When you need path parameters or method restrictions, register explicit routes in a file under route/:

// route/users.php
$app->route('/users/{id}', ['methods' => ['GET']], function($id) {
    return ['id' => (int)$id, 'name' => 'User ' . $id];
});

$app->route('/users', ['methods' => ['POST']], function($request) {
    return ['created' => true];
});

Use for: Dynamic URLs, method-specific routes, WebSocket handlers, Store table registration.

4. Namespaced routes

nsRoute and nsPathRoute add a URL prefix:

$app->nsRoute('api/v2', '/health', function() {
    return ['ok' => true];
});
// → GET /api/v2/health

$app->nsPathRoute('files', function($path) {
    return ['path' => $path];
});
// → GET /files/foo/bar/baz.txt → $path = 'foo/bar/baz.txt'

Use for: API versioning, catch-all paths (file serving, proxy).

Parameter injection

ZealPHP injects route handler arguments by name via reflection. The reflection result is cached at route registration — zero per-request overhead:

| Parameter name | Injected value                    |
| -------------- | --------------------------------- |
| $request       | ZealPHP\HTTP\Request              |
| $response      | ZealPHP\HTTP\Response             |
| $app           | ResponseMiddleware instance       |
| {param} names  | Matched URL segments              |
| any other      | null or the parameter's default   |

This means parameter order doesn't matter. function($id, $request) and function($request, $id) both work.

Return value conventions

| Return type    | Behavior                             |
| -------------- | ------------------------------------ |
| int            | HTTP status code (e.g. return 404)   |
| array / object | JSON-serialized, Content-Type set    |
| string         | HTML body                            |
| Generator      | SSR streaming (each yield sent live) |
| void + echo    | Output buffer captured               |
Live routing demos

This site uses all four routing patterns. Explore them:

You need a REST endpoint at /api/products that handles GET and POST. Which routing pattern should you use?

Key Takeaways

  • Four routing patterns: implicit public, ZealAPI, explicit, namespaced — each for a different use case
  • Path parameters ({id}) are injected by name — order doesn't matter
  • Reflection is cached at registration — zero per-request cost
  • Return type determines response format: array → JSON, string → HTML, Generator → streaming