Création et visualisation des articles - Créer un blog avec Adonis
Tags: webdev, adonisjs, tutorial, javascript
Published: 2021-08-21 10:11:25

Bonjour,

Bienvenue dans ce tutoriel pour apprendre à utiliser le [framework web Adonis](https://adonisjs.com/) ! Si tu souhaites en savoir plus sur Adonis en 1 coup œil, je t'invite à [lire cette page](https://adonisjs.com/adonisjs-at-a-glance).

Dans cette partie, on va voir ensemble comment créer un article, le lier à un utilisateur et l'afficher sur une page !

## Rappel

Ce tutoriel est la partie 4 d'une série de tutoriels qui ont pour objectif de te faire découvrir Adonis au travers la création d'un blog.

Pour lire la partie précédente, c'est par là [Création de l'authentification pour l'utilisateur - Créer un blog avec Adonis](https://dev.to/barbapapazes/creation-de-l-authentification-pour-l-utilisateur-creer-un-blog-avec-adonis-4mm8) 

Tu trouveras aussi sur GiHub l'ensemble du [code source du projet](https://github.com/Barbapapazes/create-a-blog-using-adonis) !

### Sommaire

Ce tutoriel est découpé en différente partie pour t'aider et pour éviter d'avoir des articles trop longs où l'on pourrait se perdre !

Nous allons donc voir ensemble :

- [Objectifs et mise en route - Créer un blog avec Adonis](https://dev.to/barbapapazes/objectifs-et-mise-en-route-creer-un-blog-avec-adonis-20lg)
- [Création d'un utilisateur - Créer un blog avec Adonis](https://dev.to/barbapapazes/creation-d-un-utilisateur-creer-un-blog-avec-adonis-1efi)
- [Création de l'authentification pour l'utilisateur - Créer un blog avec Adonis](https://dev.to/barbapapazes/creation-de-l-authentification-pour-l-utilisateur-creer-un-blog-avec-adonis-4mm8)
- [Création et visualisation des articles - Créer un blog avec Adonis](https://dev.to/barbapapazes/creation-et-visualisation-des-articles-creer-un-blog-avec-adonis-1d8k)
- [Gestion des articles - Créer un blog avec Adonis](https://dev.to/barbapapazes/gestion-des-articles-creer-un-blog-avec-adonis-2fc0)

Finalement, tu auras un blog fonctionnel !

## Création et visualisation des articles

On va faire ce chapitre sous la forme d'un petit exercice. En effet, on a, dans les parties précédentes notamment [Création d'un utilisateur - Créer un blog avec Adonis](https://dev.to/barbapapazes/creation-d-un-utilisateur-creer-un-blog-avec-adonis-1d67), presque tout vu pour te permettre d'être autonome sur cette partie. Je vais t'expliquer ce qu'il faut faire, te donner quelques indices puis on corrigera pas à pas ensuite !

### Ce qu'il faut faire

La liste suivante est l'ensemble des tâches ordonnées t'indiquant ce qu'il y a à faire pour arriver au bout de cette partie :

1. Créer un modèle pour nos articles
    - Un article contient les propriétés suivantes :
        - un id
        - un titre
        - du contenu
        - un propriétaire
        - une date de création
        - une date de mise à jour
    - Pour le propriétaire, on peut s'aider [de ce lien](https://docs.adonisjs.com/reference/orm/relations/belongs-to)
2. Créer une migration pour les articles
    - On fixera la limite du contenu à 1024 caractères
    - On s'assurera du lien entre le propriétaire et la table des utilisateurs
    - Aide [ici](https://docs.adonisjs.com/reference/database/table-builder#string) et [là](https://docs.adonisjs.com/reference/database/table-builder#references)
3. Créer un seeder pour les articles et éviter de devoir les créer à la main lors de la phase de développement de l'application
    - Un peu d'explication à [cette adresse](https://docs.adonisjs.com/guides/database/seeders)
4. Créer la route permettant de visualiser un ensemble d'articles et la route permettant de visualiser 1 article
    - `GET /articles`, pas d'authentification requise
    - `GET /articles/:id`, pas d'authentification requise
    - Aide [ici](https://docs.adonisjs.com/guides/controllers#resourceful-routes-and-controllers)
5. Créer le `controller` et les vues en charge de la gestion de ses 2 routes
    - Dans le `controller` de la première route, on ira chercher l'ensemble des articles présents en base de données en les paginant et on affichera le tout dans une vue. On s'assurera qu'ils soient ordonnés par date de création dans l'ordre décroissant !
        - Un peu de lecture [ici](https://docs.adonisjs.com/reference/orm/query-builder) et [là](https://docs.adonisjs.com/reference/orm/query-builder#paginate) !
    - Dans le `controller` de la seconde route, on ira chercher un article spécifique dépendant de l'id présent dans l'url, on y chargera l'utilisateur et on affichera le tout dans une vue
        - Un peu [d'aide](https://docs.adonisjs.com/guides/routing#route-parameters) et d'autres lectures [ici](https://docs.adonisjs.com/reference/orm/base-model#load) et [là](https://docs.adonisjs.com/reference/orm/base-model#static-findorfail) !

Et voilà ! Si tu as réussi à faire tout ça, bravo ! Je t'invite à bien lire les différents liens, à chercher, à essayer et à te tromper ! 

### Création du modèle

Pour le modèle, c'est assez simple. On commence par la commande pour créer un nouveau modèle :

```bash
node ace make:model article
```

Ensuite, on complète notre modèle avec les informations souhaitées :

```jsx
import { DateTime } from 'luxon'
import { BaseModel, BelongsTo, belongsTo, column } from '@ioc:Adonis/Lucid/Orm'
import User from './User'

export default class Article extends BaseModel {
  @column({ isPrimary: true })
  public id: number

  @column()
  public title: string

  @column()
  public content: string

  @column()
  public ownerId: number

  @belongsTo(() => User, { localKey: 'id', foreignKey: 'ownerId' })
  public owner: BelongsTo<typeof User>

  @column.dateTime({
    autoCreate: true,
  })
  public createdAt: DateTime

  @column.dateTime({ autoCreate: true, autoUpdate: true })
  public updatedAt: DateTime
}
```

La nouveauté concerne cette partie :

```jsx
@belongsTo(() => User, { localKey: 'id', foreignKey: 'ownerId' })
public owner: BelongsTo<typeof User>
```

On dit simplement ici qu'un article appartient à un utilisateur et que pour faire le lien, on utilise la clé `ownerId` de l'article et la clé `id` de l'utilisateur. Ensuite, cela ajoutera une propriété `owner` à l'article du type `User`.

Pour en savoir plus : [belongsTo](https://docs.adonisjs.com/reference/orm/relations/belongs-to)

### Création de la migration

Pour la création de la migration, on va aussi utiliser une commande pour la générer :

```bash
node ace make:migration article
```

La migration ressemblera à cela :

```bash
import BaseSchema from '@ioc:Adonis/Lucid/Schema'

export default class Articles extends BaseSchema {
  protected tableName = 'articles'

  public async up() {
    this.schema.createTable(this.tableName, (table) => {
      table.increments('id')
      table.string('title').notNullable().unique()
      table.string('content', 1024).notNullable()
      table.integer('owner_id').references('id').inTable('users')

      table.timestamp('created_at', { useTz: true })
      table.timestamp('updated_at', { useTz: true })
    })
  }

  public async down() {
    this.schema.dropTable(this.tableName)
  }
}
```

On remarque la création d'une clé secondaire sur `owner_id` en lien avec la table `users`. Cela permet de mettre sous contrainte la base de données pour s'assurer que l'`id` donné dans cette colonne soit effectivement existant dans la table des utilisateurs.

Aussi, on met la colonne `title` avec unique. Ainsi, on s'assure que tous les titres soient uniques directement dans les contraintes de la base de données.

Pour en savoir plus : [Table](https://docs.adonisjs.com/reference/database/table-builder)

### Création du seeder

Afin d'avoir des données dans notre base de données et pour éviter de devoir les entrer à la main à chaque fois, nous allons les inscrire une fois dans un fichier puis faire tourner ce fichier afin qu'il rentre les données dans la base de données.

Commençons par la commande suite :

```bash
node ace make:seeder article
```

Dans le dossier `database/seeders`, tu trouveras notre seeder. Dans ce fichier, nous allons utiliser les méthodes de notre modèle pour insérer de la donnée dans la base de données. Nous allons aussi récupérer notre utilisateur pour le lier à nos articles.

Dans un premier temps, on récupère notre utilisateur :

```jsx
const owner = await User.firstOrFail()
```

Ensuite, on utilise la méthode `updateOrCreateMany` qui permet de mettre à jour des articles dépendant d'une clé ou de les créer s'ils ne sont pas trouvés :

```jsx
await Article.updateOrCreateMany(uniqueKey, [])
```

Dans notre cas, le premier sera le titre puisqu'on a défini qu'il est unique selon la migration d'un article. Aussi, entre les crochets, on va y mettre l'ensemble des données que l'on va insérer ou mettre à jour. Finalement, notre fichier ressemble à cela :

```jsx
import BaseSeeder from '@ioc:Adonis/Lucid/Seeder'
import Article from 'App/Models/Article'
import User from 'App/Models/User'

export default class ArticleSeeder extends BaseSeeder {
  public static developmentOnly = true

  public async run() {
    const uniqueKey = 'title'

    const owner = await User.firstOrFail()

    await Article.updateOrCreateMany(uniqueKey, [
      {
        title: 'Article 1',
        content:
          'Nulla quis ipsum sed augue laoreet imperdiet. Fusce dapibus, lorem quis convallis fringilla, sem est maximus nulla, id egestas orci libero eget est. In maximus vestibulum nisi, dignissim aliquam orci dictum id.',
        ownerId: owner!.id,
      },
      {
        title: 'Article 2',
        content:
          'Suspendisse est mi, ultrices sit amet ullamcorper sed, semper non ipsum. Vestibulum at nisl sed purus luctus sodales. Nunc lectus lorem, vehicula in dolor pharetra, pulvinar convallis libero. Maecenas iaculis porta nibh in hendrerit. Suspendisse gravida leo non orci facilisis placerat.',
        ownerId: owner!.id,
      },
      {
        title: 'Article 3',
        content:
          'Curabitur vitae mi aliquam, pretium velit id, varius lacus. Duis id tellus nec eros semper elementum et et lectus. Phasellus eros justo, eleifend eget tellus quis, accumsan sollicitudin ex. ',
        ownerId: owner!.id,
      },
      {
        title: 'Article 4',
        content:
          'Sed id eleifend lacus. Cras est diam, commodo et erat ac, elementum volutpat dolor. Donec auctor, lorem vitae luctus aliquet, mi mi rhoncus nunc, vel vestibulum felis justo sit amet felis. Donec eleifend rhoncus nisi id pretium. Morbi sit amet auctor enim, sit amet finibus velit. In hac habitasse platea dictumst.',
        ownerId: owner!.id,
      },
      {
        title: 'Article 5',
        content:
          'Morbi eget porttitor turpis. Fusce venenatis tortor lacus, eget interdum augue pellentesque id. Vestibulum elit lorem, gravida at elit vel, molestie suscipit mauris. ',
        ownerId: owner!.id,
      },
      {
        title: 'Article 6',
        content:
          'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce congue nec tortor ut congue. Aenean a nunc nec felis sagittis auctor non a metus. Aenean euismod ligula eros, eu tempor turpis molestie sit amet. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. ',
        ownerId: owner!.id,
      },
    ])
  }
}
```

On remarque la clé `developmentOnly` à `true` qui permet de s'assurer que le seeder ne tourne qu'en développement. On ne souhaite pas de faux articles en production !

Et pour insérer nos données, il nous faut utiliser la commande suivante :

```bash
node ace db:seed
```

Et voilà, nos données sont prêtes ! Pour les visualiser, tu peux te rendre dans pgAdmin sur la table des articles !

Pour en savoir plus : [seeder](https://docs.adonisjs.com/guides/database/seeders), [updateOrCreateMany](https://docs.adonisjs.com/reference/orm/base-model#static-updateorcreatemany)

### Création des routes

La création de la route tient en unique ligne : 

```jsx
Route.resource('articles', 'ArticlesController')
```

La méthode `resource` permet de générer l'ensemble des routes dont nous allons avoir besoin ! Ainsi, pas besoin de se casser la tête ! Ainsi, on s'assure de rester dans les conventions d'Adonis tout en restant concis mais efficace dans notre code. Je te conseille d'aller lire la documentation à ce sujet qui explique et illustre très bien l'objectif d'utiliser cette méthode.

Pour en savoir plus : [resource](https://docs.adonisjs.com/guides/controllers#resourceful-routes-and-controllers)

### Création du `controller`

#### Récupération de l'ensemble des articles

Pour cela, on va utiliser le query builder de Lucid. Ainsi, pour récupérer l'ensemble des articles de manière paginé et décroissant selon la date de création, la query ressemble à cela :

```jsx
const articles = await Article.query().orderBy('created_at', 'desc').paginate(page, 3)
```

Bien sûr, il nous faudra pouvoir changer de page. Par conséquent, nous allons devoir récupérer une entrée utilisateur nommée `page` comme cela : 

```jsx
const page = request.input('page', 1)
```

Finalement, notre fonction `index` ressemble à cela : 

```jsx
public async index({ request }: HttpContextContract) {
    const page = request.input('page', 1)

    const articles = await Article.query().orderBy('created_at', 'desc').paginate(page, 3)

    return articles
  }
```

Rendons-nous sur [cette adresse](http://localhost:3333/articles). On peut ainsi observer l'ensemble de nos articles. Mais je dois bien dire que ça n'est ni très beau, ni très pratique. Mais pas de panique, la création de la vue nous permettra d'exploiter l'ensemble de ses données pour tout bien afficher !

#### Récupération d'un unique article

Pour faire cela, on commence par extraire notre `id` des paramètres de l'url : 

```jsx
const { id } = params
```

Ensuite, on effectue une simple recherche en demandant à la recherche d'échouer si l'article n'est pas trouvé. En effet, cela permet de s'assurer que l'article demandé existe et de contrôler la réponse à cet échec :

```jsx
let article: Article
try {
  article = await Article.findOrFail(id)
} catch (error) {
  console.error(error)
}
```

Sur la gestion de l'erreur, on va simplement afficher l'erreur dans la console pour le moment.

Puis à la fin, on retourne l'article :

```jsx
return article
```

Allons voir le résultat [ici](http://localhost/articles/1) ! Ce n'est pas mal, mais on ne voie pas les informations de l'utilisateur alors qu'on avait indiqué un lien entre les 2 lors de la création de notre modèle.

Pour lier les 2, il faut utiliser la méthode asynchrone `load` après la récupération de l'article :

```jsx
article = await Article.findOrFail(id)
await article.load('owner')
```

Si l'on retourne observer le résultat, on voit bien apparaitre le propriétaire sous la clé `owner`.

Pour en savoir plus : [load](https://docs.adonisjs.com/reference/orm/base-model#load), [findOrFail](https://docs.adonisjs.com/reference/orm/base-model#static-findorfail)

### Création des vues

Dans cette section, nous allons devoir créer la vue puis modifier nos `controllers` pour qu'il génère la vue en fonction des données.

Pour stocker l'ensemble de nos routes, nous allons commencer par créer un dossier nommé `articles` dans `resources/views`

#### Visionnage de l'ensemble des articles

Dans le dossier précédemment créer, ajoutons un fichier nommé `index.edge`.

Dedans, nous y trouverons cela :

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Les articles</title>
</head>
<body>
  <h1>Les articles</h1>
  <section>
    @each(article in articles)
    <article>
      <a href="{{ route('ArticlesController.show', { id: article.id }) }}">
        <h2>
          {{ article.title }}
        </h2>
      </a>
      <p>
        {{ excerpt(article.content, 100) }}
      </p>
    </article>
    @endeach
  </section>
  <div>
  @each(anchor in articles.getUrlsForRange(1, articles.lastPage))
    <a href="{{ anchor.url }}">
      {{ anchor.page }}
    </a>
  @endeach
</div>
</body>
</html>
```

C'est un fichier le plus simple possible car nous ne nous concentrons pas sur le design du site mais sur ses fonctionnalités et la découverte d'Adonis.

On trouve une boucle `@each` qui permet d'itérer au travers l'ensemble des articles et de générer le code html correspondant. La fonction route permet de générer l'url de manière dynamique en fonction de son nom et d'un paramètre. Dans notre cas, la paramètre et l'`id` de l'article. Il est possible de faire cela car l'`id` d'un article est sa clé primaire qui est par définition unique.

Dans le bas du fichier, on observe la fonction `getUrlsForRange` qui permet d'obtenir ensemble des urls et son numéro entre 2 valeurs. Cela est très pratique pour générer la pagination de notre site.

Il existe d'autres propriétés très utiles permettant de savoir si le lien est actif ou non, d'avoir le total des pages...

Ensuite, dans notre `controller`, nous devons indiquer que nous souhaitons rendre la page index des articles avec le paramètre `articles`, qui représente l'ensemble des articles :

```diff
public async index({ request, view }: HttpContextContract) {
    const page = request.input('page', 1)

    const articles = await Article.query().orderBy('created_at', 'desc').paginate(page, 3)

+    articles.baseUrl('/articles')
-    return articles
+    return view.render('articles/index', {
+      articles,
+    })
   }
```

La fonction `baseUrl` permet de s'assurer d'obtenir la bonne url sur la génération des urls dans le template lors de l'utilisation de `getUrlsForRange` par exemple.

Nous pouvons maintenant nous rendre [ici](http://localhost:333/articles) pour admirer le résultat ! Il t'est possible de jouer avec la navigation en toute simplicité !

#### Visionnage d'un unique article

Rien de sorcier ici. On commence par créer la vue, `show.edge` dans le fichier `resources/views` :

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
  </head>
  <body>
    <a href="{{ route('ArticlesController.index') }}">Voir les articles</a>

    <section>
      <h1>{{ article.title }}</h1>
      <p>{{ article.content }}</p>
      <div>par <strong> {{article.owner.pseudo}} </strong> le {{ article.createdAt.toLocaleString({ locale: 'fr-FR'}) }}, mis à jour le {{ article.updatedAt.toLocaleString({ locale: 'fr-FR'}) }}</div>
    </section>
  </body>
</html>
```

On retrouve en haut de la page, un lien permettant de retourner voir l'ensemble des articles. Dessous, on affiche l'article, son auteur et on adapte les dates à notre région !

Dans le `controller`, comme précédemment, on ajoute le fait de vouloir générer la route avec l'article que l'on a sorti de la base de données :

```diff
 public async show({ view, params }: HttpContextContract) {
   const { id } = params
 
   let article: Article
   try {
     article = await Article.findOrFail(id)
     await article.load('owner')
   } catch (error) {
     console.error(error)
+    return view.render('errors/not-found')
   }

-  return article
+  return view.render('articles/show', {
+     article,
+  })
}
```

Aussi, on a ajouté la génération de la route `not-found` si jamais on ne trouve pas l'article !

En savoir plus : [each](https://docs.adonisjs.com/reference/views/tags/each#sidenav-open), [getUrlsForRange](https://docs.adonisjs.com/reference/database/query-builder#geturlsforrange), [render](https://docs.adonisjs.com/guides/views/rendering#rendering-modes), ,[toLocalString](https://moment.github.io/luxon/api-docs/index.html#datetimetolocalestring)

## Conclusion

Et voilà pour cette quatrième partie. On a vu plus en détail la création d'un modèle et de la manière dont on peut aller chercher des données dans la base de données. On a aussi vu comment lier nos données à nos pages.

Dans la suite, on va créer les pages de création et d'édition de nos articles et les sécuriser grâce à un middleware !

N'hésite pas à commenter si tu as des questions, si ça t'a plus ou même pour me faire des retours !

Et tu peux aussi me retrouver sur [Twitter](https://twitter.com/SOUBIRAN25) ou sur [LinkedIn](https://www.linkedin.com/in/esteban25/) !

On se donne rendez-vous ici, [Gestion des articles - Créer un blog avec Adonis](https://dev.to/barbapapazes/gestion-des-articles-creer-un-blog-avec-adonis-12jb) pour la suite du tutoriel et gérer nos articles !