• Contenu
  • Bas de page
logo ouidoulogo ouidoulogo ouidoulogo ouidou
  • Qui sommes-nous ?
  • Offres
    • 💻 Applications métier
    • 🤝 Collaboration des équipes
    • 🛡️ Sécurisation et optimisation du système d’information
    • 🔗 Transformation numérique
  • Expertises
    • 🖥️ Développement logiciel
    • ♾️ DevSecOps
    • ⚙️ Intégration de logiciels et négoce de licences
      • Atlassian : Jira, Confluence, Bitbucket…
      • Plateforme monday.com
      • GitLab
      • SonarQube
    • 📚​ Logiciel de CRM et de gestion
    • 🎨 UX/UI design
    • 🌐 Accessibilité Numérique
    • 🗂️​ Démarches simplifiées
    • 📝 Formations Atlassian
  • Références
  • Carrières
    • 🧐 Pourquoi rejoindre Ouidou ?
    • ✍🏻 Nous rejoindre
    • 👨‍💻 Rencontrer nos collaborateurs
    • 🚀 Grandir chez Ouidou
  • RSE
  • Ressources
    • 🗞️ Actualités
    • 🔍 Articles techniques
    • 📖 Livres blancs
    • 🎙️ Interviews Clients
Nous contacter
✕
Image miniature 3 nouveautés Atlassian
3 nouveautés Atlassian
1 juillet 2024
Image miniature Agile SAFe
Agile SAFe : Une Approche Évolutive pour la Gestion de Projets à Grande Échelle
31 juillet 2024
Ressources > Articles techniques > Créer une API REST en Go

Créer une API REST en Go

Article écrit par Etienne R.

Pour créer une API Rest en Go, il existe de nombreux frameworks dit “http” dédiés. C’est un framework qui gère les routes HTTP (Get, Post, Update, Patch, Delete, etc…) et qui retourne des données au format JSON, XML, HTML, etc… Il en existe plusieurs dans l’éco-système de Go tels que Gorilla, Beego, Iris, Echo, Fiber, etc… De notre coté, nous allons partir sur Gin. Qui dit API, dit base de données. Pour effectuer les requêtes SQL, nous allons utiliser un ORM afin de faciliter la communication avec la base de données. Pour cela, nous allons utiliser Gorm avec SQLite (pour les besoins de la démo car il est conseillé d’utiliser une base de données comme Postgres ou MariaDB en production).

Pré-requis

Avoir Go installé et configuré sur votre environnement de développement avec la commande go version.

Remarque : cet article a été réalisé avec la version de Go 1.22.

Optionnel : pour le développement, utilisez la librairie Air qui permet de reconstruire l’application à la volée lors de la modification d’un fichier et évite de le faire manuellement (à l’image de Nodemon sur Node).

Préparatifs

Dans le répertoire de votre future application, générer le fichier de package “go.mod” avec la commande ci-dessous.

$ go mod init demo-api-gin
go: creating new go.mod: module demo-api-gin

Puis les 3 paquets mentionnés précédemment.

go get github.com/gin-gonic/gin
go get "gorm.io/gorm"
go get "gorm.io/driver/sqlite"

Modèle

Créez un dossier models avec un nouveau fichier articles.go.

Structure

// models/articles.go

package models

import (
    "gorm.io/gorm"
)

// Structure de la table
type Articles struct {
    Id          uint   `gorm:"AUTO_INCREMENT" json:"id"`
    Title       string `gorm:"not null" json:"title" binding:"required"`
    Description string `gorm:"not null" json:"description" binding:"required"`
}

On déclare la structure de notre modèle d’article avec 3 champs Id, Title et Description. On déclare également que les 2 derniers champs sont obligatoires.

Remarque : Gin utilise la librairie Go Validator avec le mot clef binding dans les options de Struct.

Ajouter ou modifier un article

func (article *Articles) SaveOrUpdateArticle(db *gorm.DB) error {
    return db.Model(&article).Save(article).Error
}

Avec la fonction Save de Gorm, on peut ajouter ou mettre à jour une ligne d’article.

Trouver tous les articles

func (article *Articles) FindArticles(db *gorm.DB) (*[]Articles, error) {
    var articles []Articles
    err := db.Find(&articles).Error
    return &articles, err
}

Avec la fonction Find de Gorm, on peut obtenir la liste de tous les articles.

Trouver un article via son id

func (articles *Articles) FindArticle(db *gorm.DB, id string) (*Articles, error) {
    var article Articles
    err := db.Take(&article, id).Error
    return &article, err
}

Avec la fonction Take de Gorm, on peut obtenir la ligne d’un article en fonction de son id.

Supprimer un article

func (article *Articles) RemoveArticle(db *gorm.DB, id string) error {
    return db.Delete(&article).Error
}

Avec la fonction Delete de Gorm, on peut supprimer une ligne d’article en fonction de son id.

Contrôleurs CRUD (Create Read Update Delete)

Créez un dossier controllers avec un nouveau fichier articles.go.

// controllers/articles.go

package controllers

import (
    "demo-api-gin/models"

    "github.com/gin-gonic/gin"
    "gorm.io/gorm"
)

type ArticlesRepo struct {
    Db *gorm.DB
}

func error500(c *gin.Context) {
    c.JSON(500, gin.H{"error": "Something wrong"})
}

func error404(c *gin.Context) {
    c.JSON(404, gin.H{"error": "Article not found"})
}

// Ajouter un article
func (repository *ArticlesRepo) PostArticles(c *gin.Context) {
}

// Obtenir la liste de tous les articles
func (repository *ArticlesRepo) GetArticles(c *gin.Context) {
}

// Obtenir un seul article via son id
func (repository *ArticlesRepo) GetArticle(c *gin.Context) {
}

// Modifier un article via son id
func (repository *ArticlesRepo) EditArticle(c *gin.Context) {
}

// Supprimer un article
func (repository *ArticlesRepo) DeleteArticle(c *gin.Context) {
}

On appel la structure de Gorm et les 2 messages génériques pour les erreurs 500 et 404 (afin d’éviter de la duplication de code). Et on met en place nos 5 contrôleurs que nous allons remplir.

Ajouter un article

func (repository *ArticlesRepo) PostArticles(c *gin.Context) {
    var articleModel models.Articles

    if err := c.ShouldBindJSON(&articleModel); err != nil {
        c.JSON(422, gin.H{"error": err.Error()})
        return
    }

    if err := articleModel.SaveOrUpdateArticle(repository.Db); err != nil {
        error500(c)
        return
    }

    c.JSON(201, gin.H{"success": articleModel})
}

On déclare une variable nommée articleModel au format de la structure Articles.

On vérifie que les valeurs des champs title et description ne sont pas vides avec la fonction ShouldBindJSON. Si les 2 champs sont mal renseignés, on retourne une erreur HTTP 422.

On appel la fonction dédiée dans le modèle, c’est-à-dire SaveOrUpdateArticle. Si tout va bien, on ajoute les valeurs en BDD et on retourne le code HTTP 201 avec un message de succès sinon on retourne une erreur générique avec le code HTTP 500.

Obtenir la liste de tous les articles

func (repository *ArticlesRepo) GetArticles(c *gin.Context) {
    var articleModel models.Articles
    articles, err := articleModel.FindArticles(repository.Db)

    if err != nil {
        error500(c)
        return
    }

    c.JSON(200, articles)
}

On déclare une variable nommée articleModel au format de la structure Articles.

On appel la fonction dédiée dans le modèle, c’est-à-dire FindArticles. Si tout va bien, on ajoute les valeurs en BDD et on retourne le code HTTP 200 avec le JSON contenant la liste des articles sinon on retourne une erreur générique avec le code HTTP 500.

Obtenir un seul article via son id

func (repository *ArticlesRepo) GetArticle(c *gin.Context) {
    id := c.Params.ByName("id")
    var articleModel models.Articles
    article, err := articleModel.FindArticle(repository.Db, id)

    if article.Id == 0 {
        error404(c)
        return
    }

    if err != nil {
        error500(c)
        return
    }

    c.JSON(200, article)
}

On récupère l’id dans une variable du même nom.

On définit l’article dans une variable articleModel au format de la structure Articles.

On appel la fonction dédiée dans le modèle, c’est-à-dire FindArticle avec l’id désiré en paramètre. Dans un premier temps, on vérifie que si l’article n’existe pas ou plus, on retourne une erreur avec code HTTP 404. Puis on vérifie que si il y a une erreur avec la requête SQL, on retourne une erreur générique avec le code HTTP 500. Si tout va bien, on le JSON de l’article en 200.

Modifier un article via son id

func (repository *ArticlesRepo) EditArticle(c *gin.Context) {
    id := c.Params.ByName("id")
    var articleModel models.Articles
    article, err := articleModel.FindArticle(repository.Db, id)

    if article.Id == 0 {
        error404(c)
        return
    }

    if err := c.ShouldBindJSON(&article); err != nil {
        c.JSON(422, gin.H{"error": err.Error()})
        return
    }

    if err != nil {
        error500(c)
        return
    }

    if err = articleModel.SaveOrUpdateArticle(repository.Db); err != nil {
        error500(c)
        return
    }

    c.JSON(200, gin.H{"success": article})
}

On fait comme dans la requête précédente sauf que l’on appel par la suite, la fonction dédiée dans le modèle, c’est-à-dire SaveOrUpdateArticle et on reprend le même principe que l’ajout d’un article (sauf pour le code HTTP qui est en 200).

Supprimer un article via son id

func (repository *ArticlesRepo) DeleteArticle(c *gin.Context) {
    id := c.Params.ByName("id")
    var articleModel models.Articles
    _, err := articleModel.FindArticle(repository.Db, id)

    if err != nil {
        error404(c)
        return
    }

    if err := articleModel.RemoveArticle(repository.Db, id); err == nil {
        error500(c)
        return
    }

    c.JSON(200, gin.H{"success": "Article #" + id + " deleted"})
}

On récupère l’id dans une variable du même nom.

On définit l’article dans une variable articleModel au format de la structure “Articles.

On appel la fonction dédiée dans le modèle, c’est-à-dire FindArticle avec l’id en paramètre. Dans un premier temps, on vérifie que si l’article n’existe pas, on retourne une erreur avec code HTTP 404. Puis on vérifie que si y a une erreur avec la requête SQL, on retourne une erreur générique avec le code HTTP 500. Si tout va bien, on retourne une 200 avec le JSON de l’article.

Routes

Créez un dossier routes avec un nouveau fichier articles.go.

// routes/articles.go

package routes

import (
    "demo-api-gin/controllers"

    "github.com/gin-gonic/gin"
    "gorm.io/gorm"
)

func ArticlesRoutes(r *gin.Engine, db *gorm.DB) *gin.Engine {
    articlesRepo := controllers.ArticlesRepo{
        Db: db,
    }

    v1Articles := r.Group("api/v1/articles")
    {
        v1Articles.POST("", articlesRepo.PostArticles)
        v1Articles.GET("", articlesRepo.GetArticles)
        v1Articles.GET(":id", articlesRepo.GetArticle)
        v1Articles.PUT(":id", articlesRepo.EditArticle)
        v1Articles.DELETE(":id", articlesRepo.DeleteArticle)
    }

    return r
}

On regroupe nos 5 requêtes dans une même variable via la fonction Group de Gin. afin de pouvoir préfixer le chemin de chaque endpoint.

Connexion à la base de données

Créez un dossier db avec un nouveau fichier db.go.

// db/db.go

package db

import (
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"

    "demo-api-gin/models"
)

func InitDb() *gorm.DB {
    db, err := gorm.Open(sqlite.Open("api.db"), &gorm.Config{})

    if err != nil {
        panic("Failed to connect database")
    }

    db.AutoMigrate(&models.Articles{})

    return db
}

Dans la fonction InitDb, on instancie une connexion avec le fichier SQLite api.db. En cas d’erreur, le programme sera interrompu par un panic. L’ORM créé tout seul la table Articles via la fonction AutoMigrate.

Point d’entrée

A la racine du projet, ajoutez le fichier d’entrée, main.go avec le code ci-dessous.

package main

import "demo-api-gin/api"

func main() {
    err := http.ListenAndServe(":3000", api.Handlers())

    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

La fonction principale main, va chercher la fonction Handlers() présente dans le fichier api.go et instancier le serveur de Gin sur le port 3000.

Tests et taux de couverture

Tester manuellement les routes, c’est assez chronophage. Pour la prospérité, nous allons les tester. Pour ce faire, nous allons utiliser la librairie officielle net/http/httptest ainsi que testify pour les assertions. Ci-dessous, le tableau récapitulatif des scénarios à tester.

Type de requèteURLCode attendu
GET/articles200
GET/articles/42404
GET/articles/1200
POST/articles422
POST/articles201
PUT/articles/42404
PUT/articles/1422
PUT/articles/1200
DELETE/article/42404
DELETE/articles/1200

Préparatifs

Installez la librairie testify pour retourner des assertions go get github.com/stretchr/testify/assert.

Dans le répertoire controllers, créez un nouveau fichier articles_test.go.

// controllers/articles_test.go

package controllers_test

import (
    "demo-api-gin/api"
    "demo-api-gin/db"
    "demo-api-gin/models"
    "net/http"
    "net/http/httptest"
    "strings"
    "testing"

    "github.com/stretchr/testify/assert"
)

var baseUrl = "/api/v1/articles"
var messageNotFound = `{"error":"Article not found"}`
var messageMissingFields = `{"error":"invalid request"}`

func init() {
    db := db.InitDb()

    var article models.Articles

    db.Migrator().DropTable(article)
    db.Migrator().CreateTable(article)

    articles := []models.Articles{{
        Title: "Titre 1", Description: "Description 1",
    }, {
        Title: "Titre 2", Description: "Description 2",
    }}

    db.Save(articles)
}

On importe les librairies puis on déclare 3 variables dont la première pour avoir la base de l’URL et les autres pour afficher des messages d’erreur génériques. Le plus important se situe dans la fonction “init” car c’est le déroulement de chaque test. Une base de données est instanciée (un fichier api.db sera instancié dans le dossier api). Par défaut, la table articles sera supprimée afin d’éviter tout conflit avec la fonction de Gorm DropTable. Puis la création de la table articles avec la fonction de Gorm CreateTable. Cette table est alimentée de 2 lignes d’aticles avec la fonction de Gorm Save. Avant de commencer nos tests, on met également en place une fonction pour appeler le routeur.

func setRouter(method string, uri string, body *strings.Reader) *httptest.ResponseRecorder {
    router := api.Handlers()
    w := httptest.NewRecorder()

    if body == nil {
        req, _ := http.NewRequest(method, uri, nil)
        router.ServeHTTP(w, req)

        return w
    }

    req, _ := http.NewRequest(method, uri, body)
    router.ServeHTTP(w, req)

    return w
}

Tester GET en 200

func TestGetArticles(t *testing.T) {
    t.Run("GET articles - 200", func(t *testing.T) {
        w := setRouter("GET", baseUrl, nil)

        assert.Equal(t, 200, w.Code)
        assert.Equal(t, `[{"id":1,"title":"Titre 1","description":"Description 1"},{"id":2,"title":"Titre 2","description":"Description 2"}]`, w.Body.String())
    })
}
  • On fait un appel de type GET sur l’url /api/v1/articles ;
  • On s’attend à recevoir un code HTTP avec la valeur 200 ;
  • On s’attend à recevoir un tableau de JSON avec la liste des articles mockés dans la fonction init.

Tester GET /id en 200 et 404

func TestGetArticle(t *testing.T) {
    t.Run("GET article - 404", func(t *testing.T) {
        w := setRouter("GET", baseUrl+"/42", nil)

        assert.Equal(t, 404, w.Code)
        assert.Equal(t, messageNotFound, w.Body.String())
    })

    t.Run("GET article - 200", func(t *testing.T) {
        w := setRouter("GET", baseUrl+"/1", nil)

        assert.Equal(t, 200, w.Code)
        assert.Equal(t, `{"id":1,"title":"Titre 1","description":"Description 1"}`, w.Body.String())
    })
}

Premier test :

  • On fait un appel de type GET sur l’url /api/v1/articles/42 ;
  • On s’attend à recevoir un code HTTP avec la valeur 400 ;
  • On s’attend à recevoir le message d’erreur générique dédié.

Second test :

  • On fait un appel de type GET sur l’url /api/v1/articles/1 ;
  • On s’attend à recevoir un code HTTP avec la valeur 200 ;
  • On s’attend à recevoir une ligne de JSON mockée dans la fonction init.

Tester POST en 422 et en 200

func TestPostArticle(t *testing.T) {
    t.Run("POST article - 422", func(t *testing.T) {
        newArticle := strings.NewReader(`{"title":"Titre 3","description":""}`)
        w := setRouter("POST", baseUrl, newArticle)

        assert.Equal(t, 422, w.Code)
        assert.Equal(t, `{"error":"Key: 'Articles.Description' Error:Field validation for 'Description' failed on the 'required' tag"}`, w.Body.String())
    })

    t.Run("POST article - 201", func(t *testing.T) {
        newArticle := strings.NewReader(`{"title":"Titre 3","description":"Description 3"}`)
        w := setRouter("POST", baseUrl, newArticle)

        assert.Equal(t, 201, w.Code)
        assert.Equal(t, `{"success":{"id":3,"title":"Titre 3","description":"Description 3"}}`, w.Body.String())
    })
}

Premier test :

  • On fait un appel de type POST sur l’url /api/v1/articles avec un contenu en JSON erroné;
  • On s’attend à recevoir un code HTTP avec la valeur 422 ;
  • On s’attend à recevoir le message d’erreur générique dédié.

Second test :

  • On fait un appel de type POST sur l’url /api/v1/articles ;
  • On s’attend à recevoir un code HTTP avec la valeur 200 ;
  • On s’attend à recevoir le message de succès attendu.

Tester PUT en 404, 422 et 200

func TestEditArticle(t *testing.T) {
    article := strings.NewReader(`{"title":"Titre test","description":"Description test"}`)

    t.Run("PUT article - 404", func(t *testing.T) {
        w := setRouter("PUT", baseUrl+"/42", article)

        assert.Equal(t, 404, w.Code)
        assert.Equal(t, messageNotFound, w.Body.String())
    })

    t.Run("PUT article - 422", func(t *testing.T) {
        w := setRouter("PUT", baseUrl+"/1", nil)

        assert.Equal(t, 422, w.Code)
        assert.Equal(t, messageMissingFields, w.Body.String())
    })

    t.Run("PUT article - 200", func(t *testing.T) {
        w := setRouter("PUT", baseUrl+"/1", article)

        assert.Equal(t, 200, w.Code)
        assert.Equal(t, `{"success":{"id":1,"title":"Titre test","description":"Description test"}}`, w.Body.String())
    })
}

Premier test :

  • On fait un appel de type PUT sur l’url /api/v1/articles/42
  • On s’attend à recevoir un code HTTP avec la valeur 404 ;
  • On s’attend à recevoir le message d’erreur générique dédié.

Second test :

  • On fait un appel de type PUT sur l’url /api/v1/articles1 avec du contenu erroné ;
  • On s’attend à recevoir un code HTTP avec la valeur 422 ;
  • On s’attend à recevoir le message d’erreur générique dédié.

Troisième test :

  • On fait un appel de type PUT sur l’url /api/v1/articles1 avec du contenu erroné ;
  • On s’attend à recevoir un code HTTP avec la valeur 200 ;
  • On s’attend à recevoir le message de succès attendu.

Tester DELETE en 404 et 200

func TestDeleteArticle(t *testing.T) {
    t.Run("DELETE article - 404", func(t *testing.T) {
        w := setRouter("DELETE", baseUrl+"/42", nil)

        assert.Equal(t, 404, w.Code)
        assert.Equal(t, messageNotFound, w.Body.String())
    })

    t.Run("DELETE article - 200", func(t *testing.T) {
        w := setRouter("DELETE", baseUrl+"/1", nil)

        assert.Equal(t, 200, w.Code)
        assert.Equal(t, `{"success":"Article #1 deleted"}`, w.Body.String())
    })
}

Premier test :

  • On fait un appel de type DELETE sur l’url /api/v1/articles/42 ;
  • On s’attend à recevoir un code HTTP avec la valeur 400 ;
  • On s’attend à recevoir le message d’erreur générique dédié.

Second test :

  • On fait un appel de type DELETE sur l’url /api/v1/articles/1 ;
  • On s’attend à recevoir un code HTTP avec la valeur 200 ;
  • On s’attend à recevoir le message de succès attendu.

Exécution des tests

Pour exécuter les tests hors de votre IDE c’est avec la commande go test ./controllers (ou go test ./controllers -v pour le mode verbeux).

Taux de couverture

Pour avoir le taux de couverture, il faut ajouter un paramètre à la commande.

$ go test ./controllers -coverprofile=cover.cov
ok      demo-api-gin/api        0.038s  coverage: 98.1% of statements

Ou en global avec “go tool”.

go tool cover -func profile.cov
        demo-api-gin/api                coverage: 0.0% of statements
        demo-api-gin            coverage: 0.0% of statements
        demo-api-gin/db         coverage: 0.0% of statements
        demo-api-gin/models             coverage: 0.0% of statements
        demo-api-gin/routes             coverage: 0.0% of statements
ok      demo-api-gin/controllers        0.039s  coverage: 80.9% of statements

Remarque: si vous utilisez Sonarqube, c’est ce fichier qu’il faut renseigner comme étant le fichier de coverage sonar.go.coverage.reportPaths=cover.cov.

Le fichier cover.cov est lisible en langage “humain” en générant le rapport en HTML avec la commande go tool cover -html=cover.cov -o cover.html.

Bonus : CORS

Si vous souhaitez communiquer avec l’API (hors requêtes GET) depuis le navigateur Internet, vous aurez ce genre d’erreur dans votre navigateur web

Blocage d’une requête multiorigines (Cross-Origin Request) : la politique « Same Origin » ne permet pas de consulter la ressource distante. Raison : l’en-tête CORS « Access-Control-Allow-Origin » est manquant. Code d’état : 201.

Pour parer à ce genre d’erreur, il faut modifier les en-têtes des requêtes HTTP.

Création du midlleware

A la racine du projet, créez un dossier middlewares avec un fichier cors.go.

// middlewares/cors

package middlewares

import (
    "github.com/gin-gonic/gin"
)

// Activation du CORS
func Cors() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
        c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
        c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, accept, origin, Cache-Control, X-Requested-With")
        c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, PUT, DELETE, OPTIONS")

        if c.Request.Method == "OPTIONS" {
            c.AbortWithStatus(204)
            return
        }

        c.Next()
    }
}

Cette fonction est appelée comme middleware dans Gin dans la fonction Handlers du fichier api.go.

func Handlers() *gin.Engine {
    // Code inchangé
    r := gin.Default()
    r.Use(middlewares.Cors())
    // Code inchangé
}

Tester le middleware

Sans oublier les tests dans le fichier cors_test.go.

package middlewares_test

import (
    "demo-api-gin/api"
    "net/http"
    "net/http/httptest"
    "testing"

    "github.com/stretchr/testify/assert"
)

func TestCors(t *testing.T) {
    t.Run("TestCors Headers", func(t *testing.T) {
        router := api.Handlers()
        w := httptest.NewRecorder()
        req, _ := http.NewRequest("GET", "/api/v1/articles", nil)
        router.ServeHTTP(w, req)

        assert.Equal(t, w.Header().Get("Access-Control-Allow-Origin"), "*")
        assert.Equal(t, w.Header().Get("Access-Control-Allow-Credentials"), "true")
        assert.Equal(t, w.Header().Get("Access-Control-Allow-Headers"), "Content-Type, Content-Length, Accept-Encoding, accept, origin, Cache-Control, X-Requested-With")
        assert.Equal(t, w.Header().Get("Access-Control-Allow-Methods"), "POST, PUT, DELETE, OPTIONS")
    })

    t.Run("TestCors Options", func(t *testing.T) {
        router := api.Handlers()
        w := httptest.NewRecorder()
        req, _ := http.NewRequest("OPTIONS", "/api/v1/articles", nil)
        router.ServeHTTP(w, req)

        assert.Equal(t, 204, w.Code)
    })
}

Premier test :

  • On fait un appel de type GET sur l’url /api/v1/articles ;
  • On s’attend à recevoir les 4 en-tètes liés au CORS.

Second test :

  • On fait un appel de type OPTIONS sur l’url /api/v1/articles/1 ;
  • On s’attend à recevoir un code HTTP avec la valeur 204.

Ainsi, on peut utiliser la fonction native de JavaScript fetch avec l’exemple ci-dessous.

async function addArticle(title, description) {
    if (title && description) {
        const body = JSON.stringify({ title, description });
        const response = await fetch(url, {
            method: "POST",
            body,
        });
        const post = await response.json();
        console.log(post)
    }
}

addArticle("ici le titre", "ici la description");

Ressources

  • Gin ;
  • Gorm ;
  • Gorm SQLite ;
  • Air ;
  • Go Validator ;
  • Testify.

À lire aussi

Fresque numérique miniature image
16 avril 2025

Fresque du Numérique

Lire la suite

intelligence artificielle Ouicommit miniature image
17 mars 2025

Ouicommit – L’intelligence artificielle en entreprise, on y est ! 

Lire la suite

Image miniature Hackathon Women in Tech
13 mars 2025

Hackathon Women in Tech :  un engagement pour une tech plus inclusive 

Lire la suite

image miniature les nouveautés Atlassian
26 février 2025

Les nouveautés Atlassian en 2025

Lire la suite

Articles associés

Fresque numérique miniature image
16 avril 2025

Fresque du Numérique


Lire la suite
intelligence artificielle Ouicommit miniature image
17 mars 2025

Ouicommit – L’intelligence artificielle en entreprise, on y est ! 


Lire la suite
Image miniature Hackathon Women in Tech
13 mars 2025

Hackathon Women in Tech :  un engagement pour une tech plus inclusive 


Lire la suite

À propos

  • Qui sommes-nous ?
  • Références
  • RSE
  • Ressources

Offres

  • Applications métier
  • Collaboration des équipes
  • Sécurisation et optimisation du système d’information
  • Transformation numérique

Expertises

  • Développement logiciel
  • DevSecOps
  • Intégration de logiciels et négoce de licences
  • Logiciel de CRM et de gestion
  • UX/UI design
  • Accessibilité Numérique
  • Démarches simplifiées
  • Formations Atlassian

Carrières

  • Pourquoi rejoindre Ouidou ?
  • Nous rejoindre
  • Rencontrer nos collaborateurs
  • Grandir chez Ouidou

SIEGE SOCIAL
70-74 boulevard Garibaldi, 75015 Paris

Ouidou Nord
165 Avenue de Bretagne, 59000 Lille

Ouidou Rhône-Alpes
4 place Amédée Bonnet, 69002 Lyon

Ouidou Grand-Ouest
2 rue Crucy, 44000 Nantes

Ouidou Grand-Est
7 cour des Cigarières, 67000 Strasbourg

  • Linkedin Ouidou
  • GitHub Ouidou
  • Youtube Ouidou
© 2024 Ouidou | Tous droits réservés | Plan du site | Mentions légales | Déclaration d'accessibilité
    Nous contacter