Intéressons-nous aujourd’hui à la création d’une API minimaliste au sein d’une application Rails avec la gem grape
.
Cette gem est un micro-framework Ruby pour la conception d’une API REST. La version 0.9.0
est actuellement la version stable de Grape.
Nous parlerons surtout aujourd’hui de la mise en place d’un CRUD avec grape
.
Nous verrons dans les prochains articles comment mettre en place un système un peu plus évolué qu’un simple CRUD ainsi que la gestion des erreurs.
Le CRUD représente 4 actions: create, read, update, delete. Ces termes désignent la création, la mise à jour, l’édition et la suppression d’un objet sur une base de donnée.
REST est une architecture d’application web. On peut la résumer à un liaison entre le client et le serveur sans état, c’est à dire que chaque requête doit contenir toutes les informations, le client doit donc garder au fur et à mesure les informations qui l’intéresse.
Nous allons prendre le cas d’un concessionnaire automobile donnant accès via un web-service à son stock de voitures. Partons du principe que le côté web est réalisé par une autre personne, nous nous concentrerons juste sur la mise à disposition de ce web-service.
A la fin de cet article, le web-service donnera la possibilité de pouvoir gérer tout le stock de voitures via le CRUD mis en place.
Nous allons pour cela créer notre application Ruby on Rails:
rails new AutoTrader
Une fois notre application créée nous allons pouvoir ajouter notre Modèle Car
qui va représenter les voitures que nous allons gérer avec notre API.
cd AutoTrader/
bundle exec rails generate model Car manufacturer:string design:string style:string
# nous pouvons maintenant lancer les migrations pour persister ce modèle dans la base de donnée
bundle exec rake db:migrate
La base de notre application est réalisée nous allons pouvoir passer à l’étape la plus importante : la création de notre API.
grape
Nous allons ajouter la gem grape
à notre Gemfile puis l’installer.
# Dans le fichier Gemfile
gem 'grape'
gem 'hashie_rails' # permet d'enlever la sécurité de strong_params, utile pour la gestion de ces validations avec Grape.
bundle install
La gem grape
est maintenant installée sur notre application. Avant de poursuivre il nous faut préciser au moteur Rails l’endroit où notre API va être écrite.
Pour cet exemple j’ai choisi de créer l’API au sein du dossier app/api
, pour ajouter ce chemin au moteur de Rails il nous faut modifier le fichier config/application.rb
comme suit:
# Dans le fichier config/application.rb
config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb')
config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')]
La gem grape
est maintenant correctement liée à l’application Rails, nous allons pouvoir rentrer dans le vif du sujet.
Nous allons mettre en place une architecture API spécifique à un numéro de version, dans le but de pouvoir par la suite créer plus facilement une “v2” par exemple.
Notre architecture se déclinera pour aujourd’hui avec deux fichiers:
app/
api/
car_world_trader/
base.rb
v1/
cars.rb
base.rb
est le fichier qui sera appelé par Rails pour le lancement de notre API. Il contient un appel vers le fichier v1/cars.rb
v1/cars.rb
contient tout notre CRUD pour la gestion des voitures.Il très important de garder les noms de dossiers et de fichiers qui correspondent aux noms de vos futurs modules et classes API, sans cela Rails ne pourra pas trouver votre API et lèvera une erreur.
base.rb
Ce fichier nous sert surtout à bien séparer notre API et permet aussi d’avoir un seul point de montage au sein de notre fichier config/routes.rb
.
Si ce n’est pas déjà fait nous allons créer le répertoire car_world_trader/
et créer le fichier base.rb
au sein de ce répertoire.
# Création du répertoire
mkdir -p app/api/car_world_trader
### Création du fichier base.rb
touch app/api/car_world_trader/base.rb
Nous allons maintenant pouvoir créer notre première Classe dépendante de grape
.
# Dans app/api/car_world_trader/base.rb
module CarWorldTrader
class Base < Grape::API
mount CarWorldTrader::V1::Cars
end
end
mount CarWorldTrader::V1::Cars
va nous permettre de monter notre API grape
que nous allons par la suite écrire dans le fichier v1/cars.rb
.Pour ce faire nous allons devoir ajouter cette route au sein du fichier config/routes.rb
. Comme spécifié plus haut c’est notre fichier base.rb
qui nous permet d’avoir un seul point d’entrée pour notre API dans les routes de l’application.
# Dans config/routes.rb
...
mount CarWorldTrader::Base => '/'
...
mount CarWorldTrader::Base => '/'
est un point de montage pour notre API car grape utilise Rack pour pouvoir fonctionner. Nous allons lui affecter un point d’entrée sur ‘/’, nous verrons par la suite que nous pouvons préfixer les routes de notre API.Nous allons aujourd’hui prendre le cas d’un CRUD très basique, sans authentification, sans gestion des erreurs et au format JSON.
Commençons par créer le fichier au sein du répertoire v1
:
# Dans le fichier app/api/car_world_trader/v1/cars.rb
module CarWorldTrader
module V1
class Cars < Grape::API
version 'v1', using: :path
format :json
prefix :api
end
end
end
version 'v1', using: :path
va représenter la version de notre API au sein de l’URL, exemple “/api/:version/”. Il existe plusieurs autre options, comme :header
qui passe le numéro de version dans le header. En savoir plus sur le versioning Grape.
format :json
indique que nous n’acceptons que du JSON en entrée de l’API.
prefix :api
permet de préfixer l’API, car dans le fichier config/routes.rb
l’api est accessible sur “/”. Maintenant grâce à cette option l’API est disponible sur “/api”
Nous allons encapsuler ces méthodes dans une resources :cars
ce qui va permettre de rendre les voitures accessible sur l’adresse “/api/:version/cars”.
# Dans le fichier app/api/car_world_trader/v1/cars.rb
...
prefix :api
resource :cars do
desc "Return list of cars"
# Récupération de la collection de toutes les voitures grâce à ActiveRecord
get do
Car.all
end
desc "Return a car"
# Récupération d'une voiture spécifique grâce au paramètre passé dans l'url
params do
requires :id, type: Integer, desc: "Car id"
end
route_param :id do
get do
Car.find(params[:id])
end
end
end
...
desc
nous sert à décrire la méthode API que nous mettons en place dans le but de rendre notre API toujours lisible et compréhensible.
route_param :id
permet de définir un namespace pour récupérer une voiture par son id.
Comme le format a été spécifié plus haut, chacune de nos deux méthodes va renvoyer les collections sous forme d’objet JSON.
Nous allons maintenant permettre aux personnes utilisant notre web-service d’ajouter des voitures sur notre application.
# Dans le fichier app/api/car_world_trader/v1/cars.rb
# au sein de la resource :cars
desc "Create a car"
params do
requires :car, type: Hash do
requires :manufacturer, type: String
requires :design, type: String
requires :style, type: String
end
end
post do
Car.create!(params[:car])
end
Nous précisons à grape
que cette méthode prend des paramètres en entrée. Au sein des paramètres nous spécifions avec un requires
ceux obligatoires. Le paramètre car
de type Hash doit comporter trois paramètres, “manufacturer”, “design”, “style”
Puis nous spécifions que cette méthode est accessible via un HTTP POST et nous créons avec ActiveRecord la voiture au sein de l’application en lui spécifiant le Hash.
Nous pouvons maintenant ajouter la méthode permettant de mettre à jour une voiture via un HTTP PUT.
# Dans le fichier app/api/car_world_trader/v1/cars.rb
# au sein de la resource :cars
desc "Update a car"
params do
requires :id, type: Integer, desc: "Status id"
requires :car, type: Hash, desc: "Your updated car"
end
put ':id' do
Car.find(params[:id]).update_attributes(params[:car])
end
Pour ce faire nous avons besoin de récupérer la voiture par cette URL “/api/:version/cars/:id”
Pour cela nous spécifions que notre méthode doit prendre deux paramètres en entrée : son id ainsi que l’objet de type Hash contenant la voiture. Comme nous voulons permettre de modifier librement la voiture il n’y a pas de paramètres requis au sein du Hash.
L’objet Voiture est ensuite mis à jour en utilisant ActiveRecord.
Un utilisateur de l’API à aussi le droit de supprimer une voiture. Pour cela il va utiliser la méthode HTTP DELETE.
# Dans le fichier app/api/car_world_trader/v1/cars.rb
# au sein de la resource :cars
desc "Delete a car"
params do
requires :id, type: Integer, desc: "Status id"
end
delete ':id' do
Car.find(params[:id]).destroy
end
La suppression d’une voiture ressemble fortement à la sélection d’une voiture par son id. Le seul changement dans cette méthode est l’utilisation de la méthode HTTP DELETE et la suppression de l’objet sélectionné avec ActiveRecord.
Une fois l’application Ruby on Rails lancée, les routes de votre API sont:
GET /api/:version/cars(.:format)
GET /api/:version/cars/:id(.:format)
POST /api/:version/cars(.:format)
PUT /api/:version/cars/:id(.:format)
DELETE /api/:version/cars/:id(.:format)
Nous avons vu aujourd’hui l’exposition d’une API très basique au sein d’une application Ruby on Rails grâce à la gem grape
. Nous nous retrouverons dans de prochains articles pour améliorer notre API avec l’ajout de la gestion des erreurs, l’ajout d’une authentification basique puis de la création d’une version 2 de notre API.
Les sources de cet article sont disponibles sur GitHub.
L’équipe Synbioz. Libres d’être ensemble.