Nouveau chapitre sur les controllers d’Adonis.js
Dans ce nouveau chapitre de notre tutoriel sur Nuxt.js + Adonis.js, nous allons parler de la mise en place des controllers d’Adonis.js et plus particulièrement du controller ‘Language’.
Voici ce que nous souhaitons réaliser d’ici la fin de ce chapitre :
Previously in « Nuxt.js + Adonis.js » 😀
Dans le précédent épisode, nous avons vu comment mettre en place les premiers modèles de notre application. La suite logique est la mise en place des premiers controllers d’Adonis.js pour commencer à mettre tous ces éléments en relation.
Sommaire du tutoriel
- Chapitre 01 – Installer Nuxt.js
- Chapitre 02 – Mise en place des templates de l’application
- Chapitre 03 – Bases de données & migrations
- Chapitre 04 – Création des modèles
- Chapitre 05 – Création des controllers
- Chapitre 06 – Authentification
- Chapitre 07 – Ajouter / Editer / Supprimer des données via l’application (à venir)
- Chapitre 08 – Conclusion (à venir)
Utilisation des controllers d’Adonis.js
Bon cette fois-ci, on plonge et on met les mains dans le cambouis. Pour commencer, nous allons nous créer un petit controller qui sera responsable de nous récupérer les languages en base de données via le modèle Language que nous avons vu au chapitre précédent. Il pourra également les mettre à jour, les créer et évidemment de les supprimer.
À vrai dire, si vous souhaitez en savoir plus sur les controllers d’Adonis.js, je vous encourage, comme toujours, à aller jeter un œil à la documentation ! Et oui, la doc ce n’est pas fait pour les chiens…
Donc, nous allons utiliser Adonis en ligne de commande pour nous prémacher le travail. N’oubliez pas qu’un développeur efficace se doit d’être fainéant d’utiliser les outils à sa disposition ! 😀
Créer des controllers d’Adonis.js via la commande make:controller
La commande make:controller
permet de créer facilement les squelettes des controllers d’Adonis.js
Afin de créer un nouveau controller, nous utilisons la commande make:controller
d’Adonis en ligne de commande.
adonis make:controller LanguageController
Grâce à cette commande, on peut voir qu’Adonis propose de créer un controller dédié aux requêtes HTTP ou pour les Websocket.
Par conséquent, si vous souhaitez changer le type de controller, il vous suffit de descendre ou monter avec les flèches haut et bas de votre clavier.
L’option sélectionnée est en bleu avec le symbole supérieur (>) devant elle.
Nous choisissons donc ‘For HTTP requests’ qui est sélectionné par défaut en appuyant sur la touche Entrée.
Adonis vient de créer pour nous le controller demandé.
Mettre à jour les controllers d’Adonis.js
Ensuite, occupons nous de récupérer les différents langages présents en base de données et de les afficher sur une page dédiée.
Pour cela, il suffit de remplacer le code généré par défaut :
Par le suivant :
'use strict'
const Language = use('App/Models/Language')
class LanguageController {
async home () {
const languages = await Language.all()
return languages.toJSON()
}
}
module.exports = LanguageController
Mise à jour du fichier de routes
Pour que ce controller puisse être appelé via l’API depuis notre frontend Nuxt, il faut en définir la route. Hop, direction notre fichier start/routes.js et nous ajoutons le bloc suivant :
Route.group(() => {
Route.get('languages', 'LanguageController.get')
}).prefix('api')
Nous utilisons ici la notion de group qui permettra par la suite de grouper plusieurs routes concernant la partie languages (pour le moment je vous l’accorde c’est un petit peu overhead !)
Ensuite, on préfixe ces routes avec api et voilà notre serveur AdonisJs est prêt à recevoir des appels GET
de notre frontend sous NuxtJs via l’url http://127.0.0.1:3333/api/languages
Ce controller avec sa fonction get
sera appelé en mode API
par notre fichier languages.vue
du répertoire /pages
de notre frontend Nuxt.
Il est à noter que ce fichier n’existe pas encore, vous avez raison ! Nous allons donc le créer.
Créer la vue associée
Nous avons besoin d’une vue associée à notre controller, à cette fin, côté frontend NuxtJs dans le répertoire /pages
, vous allez créer le fichier languages.vue
.
Dans votre éditeur favori, ajoutez le code suivant à ce fichier nouvellement créé :
<template>
<div class="container">
<div>
<logo />
<h1 class="title">
Meet The Coders
</h1>
<h2 class="subtitle">
Languages page
</h2>
<div class="links">
<a
href="#"
target="_self"
class="button--green"
>
Find Coders
</a>
<a
href="#"
target="_self"
class="button--grey"
>
Find Coders by languages
</a>
</div>
<h1 class="coder-list">
All languages
</h1>
</div>
</div>
</template>
<script>
import axios from 'axios'
import Logo from '~/components/Logo.vue'
export default {
layout: 'main',
components: {
Logo
},
async asyncData ({ req }) {
const ApiURL = process.env.apiUrl
const { data } = await axios.get(ApiURL + '/languages')
return {
languages: data
}
}
}
</script>
<style>
.blank {
width: 100px;
height: 100px;
border: 1px solid lightgray;
border-radius: 5px;
}
.coder-list {
margin-top: 1em;
}
.container {
margin: 0 auto;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
h1 {
margin-bottom: 1em;
font-family: 'Quicksand', 'Source Sans Pro', -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
display: block;
font-weight: 300;
font-size: 48px;
color: #35495e;
letter-spacing: 1px;
}
.links {
padding-top: 15px;
}
.subtitle {
font-weight: 300;
font-size: 42px;
color: #526488;
word-spacing: 5px;
padding-bottom: 15px;
}
.title {
font-family: 'Quicksand', 'Source Sans Pro', -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
display: block;
font-weight: 300;
font-size: 100px;
color: #35495e;
letter-spacing: 1px;
}
</style>
Enfin, enregistrez votre fichier avec un petit ctrl + s.
Grâce à yarn qui a dû, en arrière plan, s’occuper de mettre à jour vos fichiers, il ne vous reste plus qu’à vous rendre à l’adresse http://127.0.0.1:3000/languages
pour voir ce que cela donne.
Rien de bien folichon pour le moment, mais nous allons améliorer tout cela. Mais avant cela quelques explications sur notre nouveau fichier languages.vue
.
import axios from 'axios'
import Logo from '~/components/Logo.vue'
export default {
layout: 'main',
components: {
Logo
},
async asyncData ({ req }) {
const ApiURL = process.env.apiUrl
const { data } = await axios.get(ApiURL + '/languages')
return {
languages: data
}
}
Premièrement, nous importons la bibliothèque Axios qui va nous permettre de faire des appels au controller LanguagesController.js
(sur notre serveur AdonisJs) que nous avons défini un peu plus haut.
C’est ce que nous faisons d’ailleurs avec la méthode asyncData via un axios.get.
Ensuite, l’application va aller interroger en arrière plan l’url http://127.0.0.1:3333/api/languages
Enfin, on arrive dans le controller LanguageController
et sa fonction get
. Cette dernière fait une requête vers notre base de données, via le modèle app\Models\Language.js
, dans le but de récupérer tous les langages présents en base de données.
La fonction get
du controller LanguageController
retourne alors un set de data à notre fichier languages.vue
. Ce set data vient alors alimenter la variables languages
.
Améliorer notre page Languages
Là-dessus, nous allons à présent voir comment implémenter ce jeu de données dans notre page afin d’afficher l’ensemble des langages de programmation enregistrés en base de données, comme ci-dessous :
La future version de notre page est déjà un peu plus sexy, non ?
Pour cela, nous allons commencer à l’implémenter à l’aide de Bulma (framework CSS) retenu pour ce tutoriel.
Implémenter les migrations manquantes
Mais juste avant cela nous allons mettre en place les migrations pour compléter notre modèle de données afin de pouvoir arriver au résultat présenter ci-dessus.
Concrètement qu’allons nous ajouter comme champs à notre table ‘languages’ ?
- un champ picture : pour stocker l’image principale du langage et qui sera utilisée dans le header de la card Bulma
- un champ logo : pour stocker l’image de logo du langage en petit format
En ce qui concerne ces deux champs, le type string sera retenu.
Nous créons tout d’abord la migration pour le champ picture :
A noter, Adonis vous propose, lorsque vous demandez la création d’une migration, de sélectionner ou de créer une nouvelle table.
Dans notre cas, nous choisissons de sélectionner la table.
Sitôt que le fichier de migration est généré, nous n’avons plus qu’à le remplir.
'use strict'
/** @type {import('@adonisjs/lucid/src/Schema')} */
const Schema = use('Schema')
class LanguagesAddPictureFieldSchema extends Schema {
up () {
this.alter('languages', (table) => {
table.string('picture').nullable()
})
}
down () {
this.table('languages', (table) => {
table.dropColumns('picture')
})
}
}
module.exports = LanguagesAddPictureFieldSchema
Une fois le fichier de migration sauvegardé, on peut ensuite lancer la migration :
Il suffit ensuite d’effectuer la même opération pour le champ logo.
adonis make:migration languages_add_picture_logo
Comme précédemment, on remplit notre fichier de migration :
'use strict'
/** @type {import('@adonisjs/lucid/src/Schema')} */
const Schema = use('Schema')
class LanguagesAddLogoFieldSchema extends Schema {
up () {
this.alter('languages', (table) => {
table.string('logo').nullable()
})
}
down () {
this.table('languages', (table) => {
table.dropColumns('logo')
})
}
}
module.exports = LanguagesAddLogoFieldSchema
Et on n’oublie pas évidemment de lancer la migration ! 😉
adonis migration:run
Nota : la migration aurait pu être faite avec un seul fichier de migration, bien évidemment. C’est juste que pour ma part, je l’ai fait en deux temps 🙂
Importer les données sans se fatiguer
Comme je me tue à vous le répéter un développeur se doit d’être fainéant d’économiser ses neurone, aussi je vous livre ci-dessous les fichiers vous permettant d’importer les données nécessaires à ce chapitre.
Premièrement, le fichier csv à importer dans votre table languages de votre base de données MeetTheCoders sous PostGresql.
Deuxièmement, une gallerie contenant les images et les logos des langages de programmation que vous trouverez dans la base de données après avoir importer le fichier CSV fourni juste au-dessus.
Ces images sont à placer dans le répertoire resources/static.
Améliorer la vue
Dans le fichier languages.vue, vous allez ajouter sous la balise h1 > All languages, le code suivant.
<div class="container is-fluid">
<div class="columns is-multiline">
<div v-for="(language, index) in languages" :key="language.id+'_'+index" class="column is-one-quarter">
<div class="card">
<div class="card-image">
<figure class="image is-4by3">
<img :src="language.picture" :alt="language.title">
</figure>
</div>
<div class="card-content has-background-light">
<div class="media">
<div class="media-left">
<figure class="image is-48x48">
<img :src="language.logo" :alt="language.title">
</figure>
</div>
<div class="media-content">
<p class="title is-4">
<a :href="language.link" target="_blank">{{ language.title }}</a>
</p>
</div>
</div>
<div class="content">
<div class="tags has-addons">
<span class="tag is-light">Used by</span>
<span class="tag is-primary">42</span>
<span class="tag is-dark">coders</span>
</div>
{{ language.description }}
<br>
<small class="has-text-primary has-background-white-ter">Updated at {{ language.updated_at }}</small><br>
</div>
</div>
</div>
</div>
</div>
</div>
On se sert ici du composant Card de Bulma.
Dans les grandes lignes, voici quelques petites explications sur les modifications apportées :
- on utilise l’instruction vue.js v-for pour boucler sur l’ensemble des languages de programmation présents en base de données et les injecter dans notre composant Card de Bulma (
<div v-for="(language, index) in languages" :key="language.id+'_'+index" class="column is-one-quarter">
) - on « bind » le champ
language.picture
sur la baliseimg
. A savoir, on a stocké le nom du fichier image sous forme de string en base de données et on a placé le fichier image dans le répertoireresources/static
de notre projet. Les fichiers picture et logo ont étés récupérés sur internet et retouchés pour respecter les ratios évoqués, notamment le 4/3 pour l’image principale du langage (is-4by3
) - on « bind » également le champ
language.title
sur les balisesalt
de notre picture et de notre logo - on « bind » (décidemment) le champ
language.link
sur la balisehref
en lui associant comme texte lelanguage.title
- On crée enfin un bloc de tags associés, ce qui nous génère un petit composant sympathique (mais statique pour le moment) afin d’indiquer proprement le nombre de codeurs utilisant ce langage de programmation
Voici ce que nous obtenons à cet instant :
Là, on commence à se faire plaisir.
Et l’esthétique, bon sang !
Mais il y a deux petits soucis, la hauteur entre les « cards » qui n’est pas équivalente et tous les langages ont 42 codeurs associés.
Pour le premier point, nous allons effectuer une première optimisation. On va tronquer tous les textes descriptifs à la même longueur. C’est parti !
Chérie… ça va couper !
Pour tronquer nos textes descriptifs, nous allons nous créer un petit filtre dédié.
Dans le fichier languages.vue
, avant l’appel à async asyncData
, nous ajoutons le filtre suivant :
filters: {
textTruncate (str, length, ending) {
if (length == null) {
length = 75
}
if (ending == null) {
ending = '...'
}
if (str.length > length) {
return str.substring(0, length - ending.length) + ending
} else {
return str
}
}
},
Une fois que ce filtre est défini, nous pouvons l’utiliser à notre guise dans notre vue.
En l’occurrence, nous allons nous en servir pour tronquer le champ language.description.
Pour cela remplacer la ligne de code suivante :
{{ language.description }}
par celle-ci :
{{ language.description | textTruncate }}
On peut voir ci-dessous que cela a amélioré notre affichage, même si il n’est pas encore parfait :
La bonne solution viendra d’une discussion autour d’une issue de Bulma.
En ajoutant deux classes CSS, nous pourrons solutionner le problème de taille équivalente de card avec Bulma.
.bm--card-equal-height {
display: flex;
flex-direction: column;
height: 100%;
}
.bm--card-equal-height .card-footer {
margin-top: auto;
}
Il ne vous restera plus ensuite qu’à affecter la nouvelle classe sur votre bloc card :
<div class="card bm--card-equal-height">
<!-- content -->
</div>
Afin d’améliorer encore un peu plus l’esthétique, on va centrer notre composant informatif à base de tags Bulma. Pour cela, il suffit d’ajouter la classe is-centered
au conteneur parent.
<div class="tags has-addons is-centered">
<span class="tag is-warning">Used by</span>
<span class="tag is-primary">42</span>
<span class="tag is-dark">coders</span>
</div>
42 n’est pas la réponse universelle !
Bon ce 42 de partout, je ne sais pas vous, mais moi ça me donne la nausée !
Oui d’accord mais on n’a pas encore pluggué nos utilisateurs, donc comment s’est y donc que l’on fait, chef ?
Bon, on va ruser un peu en attendant de plugguer tout cela correctement.
Un bon petit coup de random devrait nous aider à rendre tout cela plus réaliste.
On se créé un petite ‘methods’ toute simple :
methods: {
randomNumber () {
return Math.floor(Math.random() * (100)) + 1
}
}
Et là encore, on l’utilise directement dans le code de notre vue, en remplaçant :
<div class="tags has-addons">
<span class="tag has-background-info has-text-white">Used by</span>
<span class="tag is-primary">42</span>
<span class="tag is-dark">coders</span>
</div>
par :
<div class="tags has-addons">
<span class="tag has-background-info has-text-white">Used by</span>
<span class="tag is-primary">{{ randomNumber() }}</span>
<span class="tag is-dark">coders</span>
</div>
Il y a quoi au menu ?
Tout cela c’est bien joli mais on y accède comment à notre page, hormis en tapant directement l’url à la mano ? hum ?
Ok, ok, on va y remédier de ce pas en ajoutant le menu à notre header. Pour cela vous modifier le fichier main.vue, en lui ajoutant la ligne suivante :
<li><a href="/languages">Languages</a></li>
au bloc qui compose notre menu :
<header>
<a id="logo" href="/">Meet The Coders</a>
<nav>
<ul>
<li><a href="/">Coders</a></li>
<li><a href="/languages">Languages</a></li>
<li><a href="/login">Login</a></li>
<li><a href="/signup">Signup</a></li>
</ul>
</nav>
</header>
Et là nous pouvons voir que nous avons atteint notre objectif :
Conclusion de ce chapitre sur les controllers d’Adonis.js
Et bien, nous avons vu quelques aspects sympathiques dans ce chapitre. Le point essentiel étant la compréhension des controllers d’Adonis.js.
Mais nous avons également vu l’utilisation du framework CSS Bulma.
Nous avons abordé également la mise en place de filters
et de methods
dans nos fichiers vue.
Nous aurons encore pas mal de travail sur les controllers d’Adonis.js pour mettre en place l’ensemble de notre mécanique, ici nous n’avons que survolé le concept.
Vous pouvez retrouver l’ensemble des sources sur les github dédiés à ce tutoriel :
Dans le prochain chapitre, nous parlerons d’authentification.
Si cet article vous a plu, je vous serais super reconnaissant de lui attribuer une note.
Je vous serai également éternellement reconnaissant de vous abonner à notre newsletter.
Enfin si vous avez constaté des erreurs, des oublis ou tout autre motif pour que je sois pendu haut et court, je vous encourage à utiliser les commentaires pour me le signaler.
Nota : vous pouvez aussi utiliser les commentaires aussi pour m’encourager 😀 !
Affiliation : Jérémy Mouzin – Présentation du module algorithmique
Tu te prépares à passer bientôt des entretiens techniques d’embauche (sur CodinGame peut-être ?) ou tu as échoué lors de tes derniers entretiens ?
Ne rate plus une occasion de décrocher ton 1er job en te formant à l’algorithmique dès maintenant !
Dans ce module de 60h j’ai résolu des exercices en live sur les plateformes CodeWars et CodinGame en t’expliquant toute ma façon de penser de A à Z.
Tu apprendras la méthode DECAPI et l’utilisation du pseudo-code pour éviter le syndrome de la page blanche et résoudre n’importe quel exercice facilement.
Cette formation est unique en son genre et n’existe nulle part ailleurs sur internet, prends le temps de lire le contenu complet de ce module car il peut te permettre d’accéder enfin à ton 1er job de développeur.
Accéder au module algorithmique
Astuce budget : tu peux payer en 5 fois sans frais !