---
title: Quick Start
description: Build a minimal Products page in five minutes — controller, view, navigation entry.
---

By the end of this page you'll have a working `/products` page rendered by Smarty, fed by a controller, listed in the sidebar.

## 1. Generate a controller

```bash
./nibiru -c products
```

This creates two files:

```
application/controller/productsController.php
application/view/templates/products.tpl
```

## 2. Wire the controller

Open `productsController.php` and replace the body with:

```php
<?php
namespace Nibiru;
use Nibiru\Adapter\Controller;

class productsController extends Controller
{
    public function pageAction()
    {
        View::assign([
            'title'    => 'Products — Nibiru',
            'products' => [
                ['id' => 1, 'name' => 'Marduk Gold Plating', 'price' => 99.0],
                ['id' => 2, 'name' => 'Tiamat Hull Sealant', 'price' => 49.0],
                ['id' => 3, 'name' => 'Anu Stardust Polish', 'price' => 19.5],
            ],
            'css' => Config::getInstance()->getConfig()[View::NIBIRU_SETTINGS]['smarty.css'],
            'js'  => Config::getInstance()->getConfig()[View::NIBIRU_SETTINGS]['smarty.js'],
        ]);
    }

    public function navigationAction()
    {
        JsonNavigation::getInstance()->loadJsonNavigationArray();
    }
}
```

Two methods are conventional and **always called** by the [dispatcher](/core/dispatcher/):

- `navigationAction()` — populates the global navigation menu.
- `pageAction()` — renders the page itself.

Anything matching `?_action=foo` will additionally call a `fooAction()` method between them.

## 3. Write the view

Open `application/view/templates/products.tpl`:

```smarty
{include 'shared/header.tpl'}
<body>
{include file="navigation.tpl"}

<main class="container">
  <h1>{$title}</h1>

  <table class="table">
    <thead>
      <tr><th>#</th><th>Name</th><th>Price</th></tr>
    </thead>
    <tbody>
      {foreach $products as $p}
        <tr>
          <td>{$p.id}</td>
          <td><a href="/products/detail/{$p.id}">{$p.name|escape}</a></td>
          <td>{$p.price|string_format:"%.2f"} €</td>
        </tr>
      {/foreach}
    </tbody>
  </table>
</main>

{include 'shared/footer.tpl'}
</body>
```

Variables passed to `View::assign()` show up as `{$variable}` in Smarty.

## 4. Add a navigation entry

Edit `application/settings/config/navigation/main.json` and add:

```json
{
  "label": "Products",
  "href":  "/products",
  "icon":  "shopping-bag"
}
```

The navigation is loaded by `JsonNavigation` and rendered by `navigation.tpl`.

## 5. Run it

If you have PHP's built-in server handy:

```bash
APPLICATION_ENV=development php -S localhost:8080 -t .
```

Visit <http://localhost:8080/products/>. You should see your three products with the cosmic theme of your CSS.

## Add a detail page

In the same controller:

```php
public function detailAction()
{
    $id = (int) ($_REQUEST['id'] ?? 0);
    View::assign([
        'title' => "Product #$id",
        'id'    => $id,
    ]);
}
```

`Router` already understands `/products/detail/42` and `/products/marduk-gold-plating/42` (SEO URL form — `id` and `slug` get auto-populated in `$_REQUEST`).

Create `application/view/templates/products/detail.tpl` with whatever markup you like, and you have a two-page app.

## Where to next?

- [Architecture (MMVC)](/core/architecture/) — how all the moving parts fit.
- [Modules](/core/modules/) — when to graduate from controllers + traits to a real module.
- [Database & Migrations](/core/database/) — wire the page to a real `products` table.
