Bonjour,
Bienvenue dans ce tutoriel pour apprendre à utiliser le framework web Adonis ! Si tu souhaites en savoir plus sur Adonis en 1 coup œil, je t'invite à lire cette page.
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
Tu trouveras aussi sur GiHub l'ensemble du code source du projet !
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
- Création d'un utilisateur - Créer un blog avec Adonis
- Création de l'authentification pour l'utilisateur - Créer un blog avec Adonis
- Création et visualisation des articles - Créer un blog avec Adonis
- Gestion des articles - Créer un blog avec Adonis
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, 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 :
- 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
- Un article contient les propriétés suivantes :
- Créer une migration pour les articles
- 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
- 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
-
- 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 ! - 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
- Dans le
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 :
node ace make:model article
Ensuite, on complète notre modèle avec les informations souhaitées :
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 :
@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
Création de la migration
Pour la création de la migration, on va aussi utiliser une commande pour la générer :
node ace make:migration article
La migration ressemblera à cela :
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
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 :
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 :
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 :
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 :
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 :
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, updateOrCreateMany
Création des routes
La création de la route tient en unique ligne :
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
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 :
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 :
const page = request.input('page', 1)
Finalement, notre fonction index
ressemble à cela :
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. 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 :
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 :
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 :
return article
Allons voir le résultat ici ! 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 :
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, 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 :
<!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 :
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 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
:
<!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 :
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, getUrlsForRange, render, ,toLocalString
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 ou sur LinkedIn !
On se donne rendez-vous ici, Gestion des articles - Créer un blog avec Adonis pour la suite du tutoriel et gérer nos articles !
Top comments (0)