# Homepage singleton module

In this article, we will see the creation and customization of a Twill singleton module to provide a Homepage for our application.

## Singleton module creation

In Twill terminology, a Singleton Module is globally like a classic module except that it manages only one Model.

The main differences:

* No need to handle a listing page
    
* No need to provide create, delete, and probably publish features
    
* Creation of a seeder to create the Model in the database
    

### Generation

A Singleton Module can be initiated with the CLI generator:

```bash
# PageHome module with Blocks, Translation, Revisions
php artisan twill:make:singleton PageHome -BTR
```

The output we can see in our terminal:

```php
Migration created successfully! Add some fields!
Models created successfully! Fill your fillables!
Repository created successfully! Control all the things!
Controller created successfully! Define your index/browser/form endpoints options!
Form request created successfully! Add some validation rules!

 Do you also want to generate the preview file? [no]:
  [0] no
  [1] yes
 > 

Seed created successfully!
The following snippet has been added to routes/twill.php:
-----
TwillRoutes::singleton('pageHome');
-----
To add a navigation entry add the following to your AppServiceProvider BOOT method.
-----
use A17\Twill\Facades\TwillNavigation;
use A17\Twill\View\Components\Navigation\NavigationLink;
TwillNavigation::addLink(
    NavigationLink::make()->forSingleton('pageHome')
);
-----
Migrate your database & seed your singleton module:

    php artisan migrate

    php artisan db:seed PageHomeSeeder

Enjoy.
```

### Files generated

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685614167741/e101e25d-34fc-420d-8dee-fc95368d3598.png align="left")

### What next

* Edit the Routes and create a Navigation entry
    
* Customize the fields of the Model
    
* Customize the admin Controller setup, form
    
* Morph map
    
* Seed the Model
    
* Initiate the front-end: Controller, Route, Page
    

## Twill module customization

### Routes and Navigation

**Routes configuration**

**/routes/twill.php**

```php
<?php

use A17\Twill\Facades\TwillRoutes;

TwillRoutes::singleton('pageHome');
```

**Navigation entry**

We add a NavigationLink as a sibling of our previous `PageContent` module.

**/app/Providers/AppServiceProvider.php**

```php
<?php

namespace App\Providers;

use A17\Twill\Facades\TwillNavigation;
use A17\Twill\View\Components\Navigation\NavigationLink;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        TwillNavigation::addLink(
            NavigationLink::make()
                ...
                ->setChildren([
                    NavigationLink::make()
                        ->title(Str::ucfirst(__('home')))
                        ->forSingleton('pageHome'),
                    ...
                ]),
        );
    }
}
```

### Model

In our case, we want:

* to remove the default published attribute (our homepage will always be published)
    
* a title that can be translated
    
* meta title and description that can be translated
    
* a block editor for all the content (we will focus on this part in a later article)
    

**/database/migrations/...\_create\_page\_homes\_tables.php**

```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreatePageHomesTables extends Migration
{
    public function up()
    {
        Schema::create('page_homes', function (Blueprint $table) {
            createDefaultTableFields($table, true, false);
        });

        Schema::create('page_home_translations', function (Blueprint $table) {
            createDefaultTranslationsTableFields($table, 'page_home');
        });

        Schema::table('page_home_translations', function (Blueprint $table) {
            $table->after('page_home_id', function ($table) {
                $table->string('title', 200)->nullable();
                $table->string('meta_title', 100)->nullable();
                $table->text('meta_description', 200)->nullable();
            });
        });

        Schema::create('page_home_revisions', function (Blueprint $table) {
            createDefaultRevisionsTableFields($table, 'page_home');
        });
    }

    public function down()
    {
        Schema::dropIfExists('page_home_revisions');
        Schema::dropIfExists('page_home_translations');
        Schema::dropIfExists('page_homes');
    }
}
```

> To remove the `published` attribute, you have to set the third parameter of `createDefaultTableFields()` method to `false`.

We can now run our migration to create the tables for our Module:

```bash
php artisan migrate
```

**/app/Models/PageHome.php**

```php
<?php

namespace App\Models;

use A17\Twill\Models\Behaviors\HasBlocks;
use A17\Twill\Models\Behaviors\HasTranslation;
use A17\Twill\Models\Behaviors\HasRevisions;
use A17\Twill\Models\Model;

class PageHome extends Model
{
    use HasBlocks, HasTranslation, HasRevisions;

    protected $fillable = [
        'title',
        'meta_title',
        'meta_description',
    ];

    public $translatedAttributes = [
        'title',
        'meta_title',
        'meta_description',
        'active',
    ];

    public array $publicAttributes = [
        'title',
        'meta_title',
        'meta_description',
    ];
}
```

### Controller

In our case, we want to:

* disable permalink, create, delete, publish and editor features ([https://twillcms.com/docs/modules/controllers.html#content-disable-defaults](https://twillcms.com/docs/modules/controllers.html#content-disable-defaults))
    
* configure the form:
    
    * a block editor for all the content
        
    * meta title and description that can be translated in the sidebar
        

**/app/Http/Controllers/Twill/PageHomeController.php**

```php
<?php

namespace App\Http\Controllers\Twill;

use A17\Twill\Models\Contracts\TwillModelContract;
use A17\Twill\Services\Forms\Fields\Input;
use A17\Twill\Services\Forms\Form;
use A17\Twill\Http\Controllers\Admin\SingletonModuleController as BaseModuleController;
use A17\Twill\Services\Forms\Fields\BlockEditor;
use A17\Twill\Services\Forms\Fieldset;

class PageHomeController extends BaseModuleController
{
    protected $moduleName = 'pageHomes';

    protected function setUpController(): void
    {
        $this->disablePermalink();
        $this->disableCreate();
        $this->disableDelete();
        $this->disablePublish();
        $this->disableEditor();
    }

    protected function formData($request)
    {
        return [
            'customPermalink' => route('home'),
        ];
    }

    public function getForm(TwillModelContract $model): Form
    {
        $form = parent::getForm($model);

        $form->add(
            BlockEditor::make()
                ->withoutSeparator()
        );

        return $form;
    }

    public function getSideFieldsets(TwillModelContract $model): Form
    {
        $form = parent::getSideFieldsets($model);

        $form->addFieldset(
            Fieldset::make()
                ->title('SEO')
                ->id('seo')
                ->fields([
                    Input::make()
                        ->name('meta_title')
                        ->label('Title')
                        ->translatable()
                        ->maxLength(100),

                    Input::make()
                        ->name('meta_description')
                        ->label('Description')
                        ->translatable()
                        ->maxLength(200),
                ])
        );

        return $form;
    }
}
```

### Morph map

**/app/Providers/AppServiceProvider.php**

```php
<?php

namespace App\Providers;

use Illuminate\Database\Eloquent\Relations\Relation;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Relation::enforceMorphMap([
            ...
            'pageHome' => 'App\Models\PageHome',
        ]);
    }
}
```

### Seeding

By default, Twill will automatically run the seeder when you visit the admin edition page (managed by the configuration value `twill.auto_seed_singletons`).

But you can manually run the Laravel `db:seed` command:

```bash
php artisan db:seed PageHomeSeeder
```

## Front-end with Inertia

This module aims to display a homepage on our front-end. To do so, we need:

* a Laravel Route
    
* a Laravel Controller
    
* an Inertia Page
    

### Route

**/routes/web.php**

```php
<?php

use App\Http\Controllers\App\PageHomeController;
use Illuminate\Support\Facades\Route;

Route::get('/', PageHomeController::class)->name('home');
```

### Controller

**/app/Http/Controllers/App/PageHomeController.php**

```php
<?php

namespace App\Http\Controllers\App;

use App\Models\PageHome;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Cache;
use Inertia\Inertia;
use Inertia\Response as InertiaResponse;

class PageHomeController extends Controller
{
    public function __invoke(): InertiaResponse
    {
        $locale = app()->getLocale();

        $item = Cache::rememberForever(
            'page.home.' . $locale,
            function () {
                $item = PageHome::first();

                if ($item !== null) {
                    $item->load('translations', 'blocks');
                }

                return $item;
            }
        );

        abort_if($item === null, Response::HTTP_NOT_FOUND);

        return Inertia::render('Page/Home', [
            'item' => $item->only($item->publicAttributes),
            'locale' => $locale,
        ]);
    }
}
```

### Page

**/resources/views/Pages/Page/Home.vue**

```javascript
<script setup lang="ts">
import Head from '@Theme/Head.vue'

defineOptions({
  name: 'PageHome',
})

interface Props {
  item: Model.Page
}

defineProps<Props>()
</script>

<template>
  <Head :item="item"></Head>
  <h1 class="text-center text-4xl font-semibold text-gray-900">
    {{ item.title }}
  </h1>
</template>
```

## Final result

You can now click on the permalink to open the front-end page in a new tab:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685626095394/233eefb3-25e2-4cb9-a1a6-676a48140985.png align="center")

and you should see:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685626439455/0066d912-2212-4650-a8ae-8cb1b748653a.png align="center")

---

**We now have a singleton module to handle the homepage, we will see in later articles how to improve logic, performance and handle more complex content structure with blocks**

---

> We'll do our best to provide source code of the serie on [GitHub](https://github.com/Codivores/tutorial-laravel-twill-inertia-vue3-vite-tailwind)
