Getting started

Quick start

Let’s build the first route end to end: create the project, add a controller, start the server and get a JSON response back. Five minutes.

1. The project

bash
composer create-project flytachi/winter my-app
cd my-app

Composer creates the directories and the .env for you. Requirements and the by-hand build are on the Installation page.

2. The controller

Generate one with make. The leading dot is required; the Controller suffix is added for you:

bash
php call make -c .Greet   # → main/GreetController.php

The file appears and is autoloaded straight away. Bring it to this shape — a GET /api/hello/{name} route returning JSON:

main/GreetController.php
<?php

namespace Main;

use Flytachi\Winter\Kernel\Http\Request\Annotation\PathVariable;
use Flytachi\Winter\Kernel\Http\Response\ResponseEntity;
use Flytachi\Winter\Kernel\Http\Stereotype\Controller;
use Flytachi\Winter\Kernel\Route\Annotation\GetMapping;
use Flytachi\Winter\Kernel\Route\Annotation\RequestMapping;

#[RequestMapping('api')]
class GreetController extends Controller
{
  #[GetMapping('hello/{name}')]
  public function hello(#[PathVariable] string $name): ResponseEntity
  {
      return ResponseEntity::ok(['message' => "Hello, {$name}"]);
  }
}

What is happening here:

  • #[RequestMapping('api')] on the class — a shared /api prefix for every method.
  • #[GetMapping('hello/{name}')] — the route GET /api/hello/{name}.
  • #[PathVariable] binds the {name} segment to the $name argument.
  • ResponseEntity::ok([...]) returns 200 with a JSON body.

The route needs no registration anywhere — the scanner finds it.

3. Run it

bash
php call run dev

The server listens on port 8000 on all interfaces.

The word dev turns on file watching: edit a .php and the application restarts itself. Without it you would stop and start the server after every change.

4. The result

bash
curl http://localhost:8000/api/hello/Winter
json
{"message":"Hello, Winter"}

The same address works in a browser — you will see the same JSON.

Route not found?

Look at what actually registered:

php call mapping show

If your route is not in the list, the controller never reached the scan. Check that the file is not under resources/ or storage/ (both are excluded), that the class is not abstract, and that the method is public.

5. A little more: a query parameter

Let’s add an optional ?shout=true:

main/GreetController.php
#[GetMapping('hello/{name}')]
public function hello(
  #[PathVariable] string $name,
  #[RequestParam]  bool  $shout = false,
): ResponseEntity {
  $message = "Hello, {$name}";

  return ResponseEntity::ok(['message' => $shout ? strtoupper($message) : $message]);
}
bash
curl 'http://localhost:8000/api/hello/Winter?shout=true'
json
{"message":"HELLO, WINTER"}

Do not forget the import: use Flytachi\Winter\Kernel\Http\Request\Annotation\RequestParam;

The default value is what makes the parameter optional — without it a request with no ?shout would answer 400.

`#[RequestParam]`, not `#[RequestQuery]`

For one value out of the query string you want #[RequestParam]. The similarly named #[RequestQuery] collects the whole query string into an object or an array, and on a scalar it produces 500 rather than 400 — a configuration error, not a data one.

Both are covered on the Requests page.

The whole thing

main/GreetController.php
<?php

namespace Main;

use Flytachi\Winter\Kernel\Http\Request\Annotation\PathVariable;
use Flytachi\Winter\Kernel\Http\Request\Annotation\RequestParam;
use Flytachi\Winter\Kernel\Http\Response\ResponseEntity;
use Flytachi\Winter\Kernel\Http\Stereotype\Controller;
use Flytachi\Winter\Kernel\Route\Annotation\GetMapping;
use Flytachi\Winter\Kernel\Route\Annotation\RequestMapping;

#[RequestMapping('api')]
class GreetController extends Controller
{
  #[GetMapping('hello/{name}')]
  public function hello(
      #[PathVariable] string $name,
      #[RequestParam]  bool  $shout = false,
  ): ResponseEntity {
      $message = "Hello, {$name}";

      return ResponseEntity::ok(['message' => $shout ? strtoupper($message) : $message]);
  }
}

Next

There are two ways on from here — deeper into practice, or wider into how it works.

Keep building:

Understand the machinery: