Pourquoi Rails avec GraphQL ?
Rails est un framework web Ruby très populaire pour construire des applications web et API. Cependant, avec l'augmentation de la complexité des données nécessaires à travers les API, il peut être difficile de maintenir une interface d'API RESTful efficace. Lorsqu'on a besoin de récupérer ou de modifier des ressources complexes en fonction de multiples critères et relations, alors GraphQL devient une solution idéale.
GraphQL permet aux clients de demander exactement ce qu'ils ont besoin, sans avoir à se soucier de l'ordre d'exécution ou du format de réponse. Cela réduit le nombre de requêtes réseau et améliore ainsi les performances des applications web et mobiles.
Un cas d'utilisation concret serait un application e-commerce où un utilisateur peut avoir des demandes complexes sur leurs commandes passées, comme le prix total, les détails des produits et les avis associés. Avec une API RESTful traditionnelle, il faudrait faire plusieurs requêtes pour obtenir toutes ces informations. En revanche, avec GraphQL, l'utilisateur peut simplement faire une seule requête pour obtenir exactement ce qu'il veut.
Prerequis
Voici les connaissances nécessaires et outils à installer avant de suivre ce tutoriel :
Connaissances Nécessaires
- Langage Ruby : Au moins une semaine d'expérience.
- Base de données SQL : Avoir compris la relation entre tables (JOINs).
- Rails : Connaissance intermédiaire avec les modèles, contrôleurs et vues.
- JavaScript ES6 : Pour comprendre la façon dont le front-end s'intégre à GraphQL.
Outils à Installer
- Ruby 3.0 ou plus récent
- Bundler (
gem install bundler) - PostgreSQL (ou SQLite pour des tests rapides)
- Git (pour suivre ce tutoriel et travailler sur votre propre projet)
Concepts Fondamentaux
- Schéma GraphQL :
- Le schéma est la définition complète de l'API. Il définit les types d'objets, leurs champs et les relations entre eux.
## config/initializers/graphql.rb
require 'graphql'
class Types::QueryType < GraphQL::Schema::Object
field :hello, String do
description 'A simple example field'
resolve -> { 'Hello world!' }
end
field :task, Types::TaskType, null: true do
argument :id, ID!, required: true
resolve ->(obj, args, ctx) {
Task.find(args[:id])
}
end
end
class Types::TaskType < GraphQL::Schema::Object
field :id, ID, null: false
field :title, String, null: false
field :completed, Boolean, null: false
end
class Schema < GraphQL::Schema
query(Types::QueryType)
end
Types d'Objets :
- Les types d'objets représentent les entités que vous pouvez récupérer via votre API.
Champs :
- Les champs définissent ce qui peut être demandé sur un type particulier.
Résolveurs :
- Les résolveurs sont des fonctions qui renvoient la valeur réelle d'un champ pour une requête donnée.
## app/graphql/resolvers/task_resolver.rb
class Resolvers::TaskResolver < GraphQL::Function
type Types::TaskType, null: true
argument :id, ID!, required: true
def resolve(id:)
Task.find(id)
end
end
- Mutations :
- Les mutations sont utilisées pour modifier les données sur le serveur.
## app/graphql/mutations/create_task.rb
class Mutations::CreateTask < GraphQL::Function
type Types::TaskType
argument :title, String!, required: true
argument :completed, Boolean, required: false, default_value: false
def resolve(title:, completed:)
Task.create!(title: title, completed: completed)
end
end
Mise en Pratique : Projet Fil Rouge
Nous allons construire un mini-projet complet : une application simple pour gérer des tâches. L'utilisateur pourra créer, lire, mettre à jour et supprimer des tâches.
Étape 1 : Initialisation du projet
rails new task_manager --api
cd task_manager
Étape 2 : Installation de GraphQL-ruby
bundle add graphql
Étape 3 : Configuration du schéma GraphQL
Créez un fichier config/initializers/graphql.rb :
require 'graphql'
class Types::QueryType < GraphQL::Schema::Object
field :tasks, [Types::TaskType], null: true do
resolve ->(obj, args, ctx) { Task.all }
end
field :task, Types::TaskType, null: true do
argument :id, ID!, required: true
resolve ->(obj, args, ctx) {
Task.find(args[:id])
}
end
end
class Types::TaskType < GraphQL::Schema::Object
field :id, ID, null: false
field :title, String, null: false
field :completed, Boolean, null: false
end
class Schema < GraphQL::Schema
query(Types::QueryType)
mutation(Types::MutationType) do
object Mutations::CreateTask
object Mutations::UpdateTask
object Mutations::DestroyTask
end
end
Étape 4 : Création des modèles et migrations
rails generate model Task title:string completed:boolean
rake db:migrate
Étape 5 : Ajout des mutations
Créez les fichiers suivants dans app/graphql/mutations/ :
create_task.rbupdate_task.rbdestroy_task.rb
## app/graphql/mutations/create_task.rb
class Mutations::CreateTask < GraphQL::Function
type Types::TaskType
argument :title, String!, required: true
argument :completed, Boolean, required: false, default_value: false
def resolve(title:, completed:)
Task.create!(title: title, completed: completed)
end
end
## app/graphql/mutations/update_task.rb
class Mutations::UpdateTask < GraphQL::Function
type Types::TaskType
argument :id, ID!, required: true
argument :title, String, required: false
argument :completed, Boolean, required: false
def resolve(id:, title: nil, completed: nil)
task = Task.find(id)
task.update!(title: title, completed: completed) if title || completed
task
end
end
## app/graphql/mutations/destroy_task.rb
class Mutations::DestroyTask < GraphQL::Function
type Types::TaskType
argument :id, ID!, required: true
def resolve(id:)
task = Task.find(id)
task.destroy!
task
end
end
Étape 6 : Création du contrôleur API
rails generate controller Api::V1::Tasks mutations
Ajoutez les routes dans config/routes.rb :
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
post 'tasks', to: 'tasks#create'
get 'tasks', to: 'tasks#index'
get 'tasks/:id', to: 'tasks#show'
patch 'tasks/:id', to: 'tasks#update'
delete 'tasks/:id', to: 'tasks#destroy'
end
end
end
Étape 7 : Ajout du serveur GraphQL
Créez un fichier app/graphql/graphiql_controller.rb :
class Api::V1::GraphiqlController < ApplicationController
def index
render html: <<-HTML.html_safe
<!DOCTYPE html>
<html>
<head>
<title>GraphQL Playground</title>
<link href="https://cdn.jsdelivr.net/npm/@graphiql/graphiql@0.19.3/dist/graphiql.css" rel="stylesheet" />
</head>
<body>
<div id="graphiql"></div>
<script src="https://cdn.jsdelivr.net/npm/@babel/standalone@7.24.5/browser.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/graphql-playground/build/graphiql.js"></script>
<script type="text/javascript">
ReactDOM.render(
React.createElement(GraphiQL, {
fetcher: function (graphQLParams) {
return fetch("#{request.base_url}/api/v1/tasks", {
method: 'post',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(graphQLParams),
});
}
}),
document.getElementById('graphiql')
);
</script>
</body>
</html>
HTML
end
end
Ajoutez la route pour GraphiQL dans config/routes.rb :
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
post 'tasks', to: 'tasks#create'
get 'tasks', to: 'tasks#index'
get 'tasks/:id', to: 'tasks#show'
patch 'tasks/:id', to: 'tasks#update'
delete 'tasks/:id', to: 'tasks#destroy'
get 'graphiql', to: 'graphiql#index'
end
end
end
Étape 8 : Test de l'API
Ouvrez votre navigateur et accédez à http://localhost:3000/api/v1/graphiql. Vous devriez pouvoir tester vos requêtes GraphQL.
Erreurs Frequentes et Debugging
Erreur 1 : Le résolveur renvoie nil
## ❌ Mauvais
def resolve(obj, args, ctx)
Task.find(args[:id])
end
## ✅ Correct
def resolve(obj, args, ctx)
task = Task.find_by(id: args[:id])
raise GraphQL::ExecutionError.new("Task not found") if task.nil?
task
end
Erreur 2 : La requête GraphQL ne fonctionne pas
## ❌ Mauvais
query {
tasks {
id
title
}
}
## ✅ Correct
query {
tasks {
id
title
completed
}
}
Erreur 3 : Le serveur GraphQL renvoie une erreur interne
## ❌ Mauvais
class Mutations::CreateTask < GraphQL::Function
type Types::TaskType
argument :title, String!, required: true
argument :completed, Boolean, required: false, default_value: false
def resolve(title:, completed:)
Task.create!(title: title, completed: completed)
end
end
## ✅ Correct
class Mutations::CreateTask < GraphQL::Function
type Types::TaskType
argument :title, String!, required: true
argument :completed, Boolean, required: false, default_value: false
def resolve(title:, completed:)
Task.create!(title: title, completed: completed)
end
rescue ActiveRecord::RecordInvalid => e
GraphQL::ExecutionError.new(e.message)
end
Pour Aller Plus Loins
- Authentification et Sécurité : Utilisez JWT pour l'authentification des utilisateurs.
- Préparation aux performances : Utilisez batching et caching avec GraphQL.
- Intégration Front-end : Utilisez Apollo Client pour intégrer GraphQL dans un application React.
Défi pratique : Créez une application de gestion de projets en utilisant GraphQL. L'utilisateur devrait être able à créer, lire, mettre à jour et supprimer des projets et leurs tâches associées.