• chevron_right

      Comment coder une API RESTful ?

      news.movim.eu / Korben · Thursday, 20 October, 2022 - 07:01 · 12 minutes

    — Article en partenariat avec talent.io —

    Certains pensent que le monde se divise en deux avec d’un côté le bien et de l’autre le mal. Mais la vérité est ailleurs surtout pour les développeurs qui savent que le monde se divise en réalité entre le Frontend et le Backend.

    Et pour que ces 2 faces d’une même pièce puissent « discuter », il nous faut des API. API, ça veut dire Application Programming Interface et c’est tout simplement un ensemble d’instructions permettant aux applications de communiquer entre elles. Maintenant des types d’API, il y en a des tonnes, mais je vous propose qu’aujourd’hui on s’intéresse aux API qui respectent le standard architectural REST.

    En effet, quand on travaille en tant que développeur, on ne peut pas passer à côté des API REST, tant cette pratique est très répandue. Si vous débutez dans votre métier, c’est forcement quelque chose que vous devez apprendre et maitriser. De la même manière, quand on débute sa carrière de développeur ou qu’on a déjà beaucoup d’années au compteur mais qu’on ne connaît pas sa valeur sur le marché de l’emploi, il est bon de connaître la grille des salaires en France. Ça tombe bien puisque talent.io a mis en ligne une étude des salaires de la tech en 2022 pour que vous puissiez déterminer si votre salaire est correct ou sous évalué.

    Je vous invite à vous plonger dedans afin de savoir où vous vous situez sur cette grille des salaires.

    C’est quoi une API RESTful ?

    REST, ça veut dire « Representational State Transfer ». Lorsqu’une API respecte les contraintes de REST, on parle alors d’une API RESTful.

    Quand un développeur veut créer une API RESTful, il doit alors respecter les contraintes édictées par REST, notamment sur le fait que son API doit fonctionner au travers du protocole HTTP et de ses verbes (GET, POST…etc.) et que les requêtes et les réponses de l’API soient dans un format texte tels que JSON, HTML, XML…etc.

    Les API RESTful sont alors mises à disposition des applications via ce qu’on appelle des endpoints. Un endpoint est un assemblage de 2 choses : une URI et un verbe HTTP.

    Admettons que j’ai une API qui permette de lister des utilisateurs, mais également d’en créer des nouveaux.

    L’URI de mon API est la suivante :

    https://url.com/api/user

    Le but du jeu, c’est d’appeler par exemple ce endpoint avec le verbe GET pour obtenir une liste de tous les utilisateurs.

    GET: /user/

    Ou pour ajouter un nouvel utilisateur, on peut utiliser le verbe POST comme ceci, en transmettant un bout de JSON contenant toutes les informations liées à l’utilisateur qu’on souhaite créer (nom, prénom…etc.) :

    POST: /user/

    L’idée là, c’est de vous montrer qu’on peut interagir avec l’API à l’aide de différents verbes HTTP. Get peut être assimilé au verbe « lire », POST au verbe « créer », PUT et PATCH au verbe « Mettre à jour » et DELETE au verbe « supprimer ». C’est aussi simple que cela. Et en retour le serveur peut alors répondre avec des codes de status HTTP classiques comme 200, 201, 2002…etc. pour dire que tout est OK, ou des codes de redirection 3xx, d’erreur client 4xx ou d’erreur serveur 5xx.

    En ce qui concerne le format utilisé par les requêtes et les réponses, vous pouvez utiliser du texte, peu importe son format (html, xml.etc.), mais par convention, on utilise surtout du JSON (JavaScript Object Notation).

    Et JSON ?

    Le JSON a la particularité d’être facile à lire et à écrire pour les humains et est utilisé pour enregistrer des données dans des bases de données, mais également pour transmettre ces données entre les applications. C’est pourquoi on l’utilise dans les API RESTful.

    Le JSON est une collection de paires nom / valeur qui sont ordonnées dans une liste. Ainsi les valeurs contenues dans le JSON peuvent être une chaine de caractère (string), un nombre, mais également des objets JSON, des tableaux de valeurs (Array), des booléens (true / false) ou la fameuse valeur null.

    Les contrôles hypermedia

    Je ne vais pas les aborder en détail pour ne pas alourdir cette initiation débutant mais sachez que pour qu’une API soit RESTful, elle doit en plus de tous ces aspects, intégrer également la notion de contrôle hypermedia (Hypermedia as the Engine of Application State – HATEOAS). C’est un attribut de REST qui permet de renvoyer des liens hypertextes afin d’indiquer les actions disponibles directement dans les réponses JSON

    Voilà concernant les grands principes des API RESTful.

    Comment créer votre première API RESTful ?

    Maintenant je vous propose qu’on apprenne à en créer une de zéro. Vous allez voir, c’est super simple.

    Pour cette démo, j’ai choisi d’utiliser Python comme d’habitude. Et je vais importer dans mon code le framework Flask qui va me permettre de faire monter mon API RESTful très facilement. Installez Flask comme ceci :

    pip3 install flask-restful

    Pour tester l’API, nous allons utiliser un outil qui permet à la fois de balancer nos requêtes et de voir si les réponses correspondent bien. L’un des plus connus s’appelle Postman et c’est gratuit en version de base pour tester. L’application Desktop de Postman est disponible ici .

    Ensuite, nous allons définir ce que va faire notre API. Ce sera quelque chose d’assez simple permettant de créer, supprimer, lire et mettre à jour des données. Admettons que j’ai un site sur lequel je dois référencer des produits. Chacun de ces produits aura un id, un nom, un prix et une catégorie.

    Le code de votre première API

    Je vais donc créer un fichier productAPI.py et je vais importer Flask comme ceci avec ses imports spécifiques aux API et au parsing JSON :

    from flask import Flask
    from flask_restful import Resource, Api, reqparse
    
    app = Flask(__name__)
    api = Api(app)

    Puis je vais créer une espèce de mini base de données JSON directement dans mon code afin d’y référencer mes produits :

    products = [
        { "id": 1, "name": "Stylo 4 couleurs", "price": 10, "category": "Papeterie" },
        { "id": 2, "name": "Cahier vert", "price": 20, "category": "Papeterie" },
        { "id": 3, "name": "Télévision 4K", "price": 30, "category": "Tech" },
        { "id": 4, "name": "Souris sans fil", "price": 40, "category": "Tech" },
        { "id": 5, "name": "Ananas", "price": 50, "category": "Alimentaire" },
        { "id": 6, "name": "Kinder Bueno", "price": 60, "category": "Alimentaire" },
        { "id": 7, "name": "Chemise de président", "price": 70, "category": "Textile" },
        { "id": 8, "name": "T-shirt de punk", "price": 80, "category": "Textile" },
        { "id": 9, "name": "Tournevis bleu", "price": 90, "category": "Bricolage" },
        { "id": 10, "name": "Marteau rouge", "price": 100, "category": "Bricolage" }
        ]

    Maintenant on va créer une classe Product qui sera vu comme un endpoint au niveau de notre API. Et dans cette class, nous allons placer nos 4 fonctions correspondantes aux 4 verbes HTTP dont je vous ai parlé plus haut.

    class Product(Resource):
    
        def get(self, id):
    
        def post(self, id):
    
        def put(self, id):
    
        def delete(self, id):

    La structure de base de notre API est OK. Maintenant, on va définir chacune de ces fonctions en commençant par la fonction get utilisée pour récupérer les informations d’un produit à partir de son ID. Comme vous pouvez le voir, on fait une boucle « for » qui va parcourir l’ensemble des produits et si l’ID correspond à ce qui spécifié dans la requête, on renvoie l’objet « product » avec son code HTTP 200 et si ce n’est pas le cas, on renvoie un message « Produit introuvable » accompagné d’un code d’erreur HTTP 404 qui correspond à une ressource non trouvée.

        def get(self, id):
            for product in products:
                if (id == product["id"]):
                    return product, 200
            return "Produit introuvable", 404

    La fonction suivante va nous permettre de créer un nouveau produit. La donnée utilisée dans la requête étant structurée en JSON, cette fonction va utiliser le parseur de Flask pour extraire toutes les données du JSON et les ajouter à notre tableau de produits :

        def post(self, id):
            parser = reqparse.RequestParser()
            parser.add_argument("name")
            parser.add_argument("price")
            parser.add_argument("category")
            args = parser.parse_args()
    
            for product in products:
                if (id == product["id"]):
                    return "Ce produit {} existe deja".format(id), 400
    
            product = {
                "id": id,
                "name": args["name"],
                "price": args["price"],
                "category": args["category"]
            }
            products.append(product)
            return product, 201

    Une fois que le produit est créé, on retourne le code 201 qui veut dire que la création s’est bien déroulée. Et si le produit existait déjà avec cet ID, on renvoie le code 400 avec un message d’erreur.

    La fonction put suivante est assez similaire puisqu’elle permet à la fois de créer un nouveau produit (si l’id n’est pas encore utilisé) ou de mettre à jour un produit existant.

        def put(self, id):
            parser = reqparse.RequestParser()
            parser.add_argument("name")
            parser.add_argument("price")
            parser.add_argument("category")
            args = parser.parse_args()
    
            for product in products:
                if (id == product["id"]):
                    product["name"] = args["name"]
                    product["price"] = args["price"]
                    product["category"] = args["category"]
                    return product, 200
    
            product = {
                "id": id,
                "name": args["name"],
                "price": args["price"],
                "category": args["category"]
            }
            products.append(product)
            return product, 201

    Enfin, vous l’aurez compris, la fonction delete permettra de supprimer un enregistrement à partir de son ID.

        def delete(self, id):
            global products
            products = [product for product in products if product["id"] != id]
            return "{} is deleted.".format(id), 200

    Une fois ces fonctions définies, il ne reste plus qu’à spécifier le format de l’URI qui sera utilisé pour appeler l’API et lancer l’API (en mode debug pour démarrer). Le paramètre <int:id> permet d’indiquer que dans le chemin du endpoint, on peut ajouter une variable acceptée par l’API (ici l’ID du produit).

    api.add_resource(Product, "/product/<int:id>")
    app.run(debug=True)

    Tester l’API avec Postman

    Enfin, il ne reste plus qu’à lancer le script python à l’aide de la commande suivante :

    python3 productAPI.py

    Le serveur web de Flask se lancera alors et vous verrez apparaitre une URL locale que vous pourrez appeler dans Postman pour ensuite tester votre API.

    Voici le code complet du script pour information.

    from flask import Flask
    from flask_restful import Resource, Api, reqparse
    
    app = Flask(__name__)
    api = Api(app)
    
    products = [
        { "id": 1, "name": "Stylo 4 couleurs", "price": 10, "category": "Papeterie" },
        { "id": 2, "name": "Cahier vert", "price": 20, "category": "Papeterie" },
        { "id": 3, "name": "Télévision 4K", "price": 30, "category": "Tech" },
        { "id": 4, "name": "Souris sans fil", "price": 40, "category": "Tech" },
        { "id": 5, "name": "Ananas", "price": 50, "category": "Alimentaire" },
        { "id": 6, "name": "Kinder Bueno", "price": 60, "category": "Alimentaire" },
        { "id": 7, "name": "Chemise de président", "price": 70, "category": "Textile" },
        { "id": 8, "name": "T-shirt de punk", "price": 80, "category": "Textile" },
        { "id": 9, "name": "Tournevis bleu", "price": 90, "category": "Bricolage" },
        { "id": 10, "name": "Marteau rouge", "price": 100, "category": "Bricolage" }
        ]
    
    class Product(Resource):
        def get(self, id):
            for product in products:
                if (id == product["id"]):
                    return product, 200
            return "Product not found", 404
    
        def post(self, id):
            parser = reqparse.RequestParser()
            parser.add_argument("name")
            parser.add_argument("price")
            parser.add_argument("category")
            args = parser.parse_args()
    
            for product in products:
                if (id == product["id"]):
                    return "Ce produit {} existe deja".format(id), 400
    
            product = {
                "id": id,
                "name": args["name"],
                "price": args["price"],
                "category": args["category"]
            }
            products.append(product)
            return product, 201
    
        def put(self, id):
            parser = reqparse.RequestParser()
            parser.add_argument("name")
            parser.add_argument("price")
            parser.add_argument("category")
            args = parser.parse_args()
    
            for product in products:
                if (id == product["id"]):
                    product["name"] = args["name"]
                    product["price"] = args["price"]
                    product["category"] = args["category"]
                    return product, 200
    
            product = {
                "id": id,
                "name": args["name"],
                "price": args["price"],
                "category": args["category"]
            }
            products.append(product)
            return product, 201
    
        def delete(self, id):
            global products
            products = [product for product in products if product["id"] != id]
            return "{} is deleted.".format(id), 200
    
    api.add_resource(Product, "/product/<int:id>")
    app.run(debug=True)

    Côté Postman, un simple GET se forme ainsi et renvoie l’intégralité du JSON propre au produit (grâce à son ID) :

    Pour faire un POST, il faut bien penser à indiquer qu’on souhaite transmettre un contenu de type « RAW » au format JSON et appeler le endpoint avec l’ID de notre nouveau produit (ce n’est pas l’idéal bien sûr, car le principe d’un nouvel ID c’est qu’il soit généré directement par le code de l’API, mais c’est pour illustrer mon exemple).

    Ensuite pour la mise à jour avec le verbe PUT, ça donne ça…

    Et pour la suppression, on obtient ce genre de retour :

    Et ensuite ?

    Vous l’aurez compris, ce n’est vraiment pas compliqué à développer. Ici on est sur un exemple simple, mais ensuite, vous devrez pourquoi pas sortir vos données d’une base de données, penser à correctement gérer les erreurs et faire preuve de rigueur dans la structure de vos endpoints. Par exemple, dans mon endpoint, l’URI est xxxx/product/. J’aurais pu faire mieux en respectant la convention et en mettant/products/ au pluriel. Car en cas de GET, je peux aussi demander à récupérer l’ensemble des produits existants. Dans ce cas, je dois modifier ma fonction GET comme ceci :

        def get(self, id):
            if (id == 0):
                return products, 200
            else:
                for product in products:
                    if (id == product["id"]):
                        return product, 200
                return "Product not found", 404

    Ainsi, en passant l’id 0 lors de mon GET, je récupérerai alors tous les produits.

    N’oubliez pas également de documenter l’ensemble de votre API afin de vous y retrouver et surtout d’indiquer à d’autres développeurs, comment l’intégrer. Penchez vous également sur l’aspect HATEOAS pour intégrer les actions disponibles dans les réponses de l’API REST. Pour notre exemple, on pourrait ainsi avoir quelque chose qui ressemble à ça :

    "links": [ 
        {"rel": "product", "method":"post", "href":"http://example.org/product/1"},
        {"rel": "product", "method":"put", "href":"http://example.org/product/1"}, ... 
    ]

    Voilà, j’espère que cette petite initiation et explications aux API RESTful vous aura intéressé et donné envie de vous y mettre plus sérieusement. Peut-être changer un peu votre façon de coder, voire carrément changer de job pour trouver une équipe plus agile et plus au fait des pratiques de dev modernes. Et pourquoi pas en profiter pour continuer à évoluer dans les technologies, ou obtenir un meilleur salaire. C’est une démarche qui peut s’avérer assez compliquée, stressante parfois, tant on a envie de trouver un travail qui nous corresponde. On peut souhaiter avoir un travail plus proche de chez soi voire en télétravail complet, un meilleur salaire, une meilleure ambiance au travail, ou même tout ça à la fois (mais ce n’est pas forcément facile à trouver).

    Heureusement, talent.io est là pour vous aider. Il vous suffit de créer un compte sur la plateforme talent.io en quelques clics pour ensuite recevoir des offres de la part d’entreprises qui correspondent à vos critères précis et qui affichent un salaire d’entrée de jeu. talent.io est vraiment le moyen le plus simple de trouver son prochain job tech, d’ailleurs les inscrits trouvent leur emploi en 20 jours en moyenne.