Пошаговое руководство по созданию RESTful API

Пошаговое руководство по созданию RESTful API Программирование и разработка

Интерфейс прикладного программирования (API) — это программный шлюз, который позволяет различным программным компонентам взаимодействовать друг с другом. API-интерфейсы помогают раскрыть возможности приложения внешнему миру, обеспечивая программный доступ к их данным.

Рассмотрим случай приложения, предоставляющего биржевую или погодную информацию. Создание и предоставление API для таких систем позволит другим программно извлекать данные, предлагаемые этими системами, например, прогнозы погоды для указанного местоположения. Тот же пример можно распространить на различные другие варианты использования. Широко используемые системы, такие как YouTube, Reddit, Google Maps и другие, предоставляют API-интерфейсы, позволяющие авторизованным клиентам получать доступ к ресурсам, предоставляемым этими системами.

Рассмотрим случай приложения, предоставл

С годами API развивались и приобретали некоторые функции, делающие их еще более эффективными и полезными. Современные API соответствуют стандартам HTTP, что делает их удобными для разработчиков и простыми в использовании.

Передача репрезентативного состояния (REST) ​​— это стиль веб-архитектуры, предложенный в 2000 году для решения проблемы экспоненциальной масштабируемости сети. REST требует, чтобы сервер выполнял запрос клиента, предоставляя представление ресурса, которое содержит ссылки для изменения состояния системы и получения представления новых ресурсов. Архитектурный стиль REST основан на ограничениях, таких как взаимодействие клиент-сервер, возможность кэширования, безгражданство и другие, и составляет основу современного Интернета.

API, основанные на архитектуре REST, удачно называются RESTful API. Они обычно используют HTTP в качестве базового протокола с методами HTTP (GET, POST, PUT, DELETE) для управления ресурсами.

Создание RESTful API в Go

В Educative у нас есть разнообразный каталог интерактивных и практических курсов по API, и тема этого блога — как создать RESTful API.

Мы будем использовать Go в качестве языка программирования. Однако концепции, представленные в этом блоге, можно применять и к другим языкам программирования.

Код намеренно представлен в упрощенном виде, чтобы помочь учащимся легко понять концепции. Мы не обрабатывали ошибки и не пытались оптимизировать код для облегчения чтения и понимания.

Мы рассмотрим пример создания API для управления курсами. Мы будем следовать пошаговому подходу, и к концу этого блога у нас будет полностью функциональный API в Go. В видео ниже показано, как можно запустить API на платформе Educative, следуя пошаговому подходу, и к концу этого блога у нас будет полностью функциональный API в Go. Видео ниже показывает, как можно запустить API на платформе Educative. форма

Как показано на видео выше, мы можем запускать сервер и делать GETзапросы с помощью браузера. А как насчет других HTTPзапросов, таких как POSTи DELETE? Мы можем использовать один из многофункциональных виджетов, доступных на платформе Educative, виджет API. Его можно использовать для выполнения запросов API с различными HTTPметодами и параметрами. Мы представим демонстрацию в конце этого блога.

Шаг 1. Конечные точки API

Прежде чем перейти непосредственно к коду, было бы полезно ознакомиться с тем, что мы пытаемся создать, и с тем, как структурировать наш API. Мы создаем каталог курсов в памяти и должны иметь возможность выполнять операции CRUD с курсами. Нам нужно сопоставить эти операции CRUD с конечными точками API при разработке API.

В двунаправленной связи конечная точка представляет собой один конец связи, по существу определяя, как получить доступ к API с помощью URL-адреса. Клиент может отправлять запросы конечной точке для получения ресурсов или выполнения операций. На приведенном выше рисунке показано, что конечные точки состоят из двух частей: базового URL-адреса и имени конечной точки.

Присвоение имен конечным точкам важно, и соблюдение рекомендаций по именованию конечных точек поможет другим использовать API. Как правило, имена должны быть существительными (в основном во множественном числе), основанными на содержимом ресурса, а не глаголом, определяющим действие, выполняемое с ресурсом. Для нашего API одно из возможных соглашений об именах выглядит следующим образом:

HTTP- запрос Имя конечной точки Описание
GET /courses Возвращает список всех курсов.
GET /courses/:id Извлекает информацию о конкретном курсе, указанном в параметре ID.
POST /courses Добавляет новый курс в коллекцию. Мы можем заметить, что имя конечной точки остается прежним.
PUT /courses/:id Обновляет курс с указанным идентификатором. Мы также можем использовать PATCH для частичного обновления курса.
DELETE /courses/:id Удаляет определенный курс, предоставляя его идентификатор.

Мы назвали нашу конечную точку как /coursesсуществительное во множественном числе, основанное на содержимом ресурса, доступного в конечной точке. Мы также использовали косую черту /для представления иерархии и /courses/:idпредставления определенного курса вместо коллекции. Кроме того, мы используем HTTPметоды для указания действий, выполняемых над ресурсом. Наконец, мы также можем заметить, что некоторые действия выполняются с отдельным ресурсом, а не со всей коллекцией.

Веб-приложение в Go

Читайте также:  Как извлечь URL-адреса из строки в JavaScript?

Прежде чем двигаться дальше, давайте кратко вспомним о написании веб-приложений на Go. Go предоставляет net/httpпакет. Приведенный ниже код предназначен для веб-приложения «hello-world» в Go.

package main
import (
    «fmt»
    «net/http»
)
//the handler function
func getHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, «Hello from Educative!»)
}
func main() {
    http.HandleFunc(«/», getHandler)
    http.ListenAndServe(«:8080», nil)
}

Поскольку мы создаем отдельную программу на Go, нам нужно поместить наш код в package main. Внутри пакета mainначнем с mainфункции, точки входа в наш код. Сначала функция определяет функцию-дескриптор с помощью http.HandleFunc, которая указывает, что все запросы должны обрабатываться функцией с именем getHandler. mainЗатем функция использует для http.ListenAndServeзапуска HTTPсервера, прослушивающего указанный порт, 8080.

Это чисто, просто и мощно. С этими строками наш многопоточный HTTP сервер запущен и работает, прослушивая HTTPзапросы и направляя их нашему обработчику. Далее, давайте сосредоточимся на нашей функции дескриптора, которая определена непосредственно перед основной функцией. Сигнатура функции показывает, что она принимает http.ResponseWriterи http.Requestв качестве аргументов. Их легче понять. Переменная http.Requestпредставляет запрос клиента во время записи для http.ResponseWriterотправки HTTPответа клиенту.

Внутри функции мы просто пишем строку клиенту.

Шаг 2: GETвсе курсы

Теперь мы готовы реализовать нашу первую конечную точку. Сначала нам нужна структура данных для хранения информации о курсах. В нашем случае на данном этапе будет достаточно структуры (с полями IDи ). TitleКонечно, при необходимости мы можем добавить в структуру другие поля.

//other fields can be added
type course struct {
    ID     string  `json:»id»`
    Title  string  `json:»title»`
}
//courses is a slice of course type
var courses = []course{
    {«100»,»Grokking Modern System Design «},
    {«101″,»CloudLab: WebSockets-based Chat Application using API Gateway»},
}

Структура данных для хранения информации о курсах

Такие теги, как json:»id«помогают нам сериализовать содержимое структуры в JSON, используя имена полей в нижнем регистре, что является распространенным стилем для JSON. Мы также создаем экземпляр слайса courses, содержащего несколько примеров курсов.

Итак, у нас есть контент, готовый к использованию, и нам нужен способ доставки его клиентам, запрашивающим его через HTTP. Мы можем обновить наш обработчик из приведенного выше кода «hello-world», чтобы отправлять фрагмент coursesвместо строки.

//we are now sending courses instead of a string
func getHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, «<h1>%s</h1>», courses)
}
func main() {
    http.HandleFunc(«/courses/», getHandler)
    log.Fatal(http.ListenAndServe(«:8080», nil))
}

Обновлен обработчик для отправки фрагмента курсов

Это было очень просто, но, к сожалению, мы все еще не там. Хотя наш код работает как надо, ответ предоставляется клиенту в формате HTML, что удобно для рендеринга в браузере, но не для потребления. API-интерфейсы RESTful обычно предоставляют ответ в формате JSON, и мы можем легко маршалировать данные с помощью encoding/jsonпакета. Обновленная функция выглядит следующим образом.

//Marshaling data and setting content-type header
func getHandler(w http.ResponseWriter, r *http.Request) {
    jsonData, _ := json.Marshal(courses)
    w.Header().Set(«Content-Type», «application/json»)
    fmt.Fprintf(w, «%s», jsonData)
}

Маршалинг данных с помощью пакета encoding/json

На этом мы закончили с нашей первой GETконечной точкой. Если мы запустим сервер и получим доступ к /coursesконечной точке на порту 8080из нашего браузера, мы увидим ответ JSON. Мы также можем заметить, что возвращаемый вывод содержит имена полей в нижнем регистре, которые указаны как теги с определением структуры.

Представляем маршрутизатор

Хотя мы можем продолжить создание нашего API с помощью net/httpпакета, обработка связанных случаев может оказаться сложной. Например, нам нужно использовать одну и ту же конечную точку /coursesс запросами GETи POST. Согласно нашему коду выше, для обоих запросов будет вызываться один и тот же обработчик. Один из способов обойти это может заключаться в том, что внутри обработчика мы используем что-то вроде if r.Method == http.MethodGet {обработки разных методов. Чтобы упростить разработку, мы можем использовать сторонний пакет, такой как github.com/gorilla/mux. Затем с помощью gorillaмы можем обновить код следующим образом:

var mux *http.ServeMux = http.NewServeMux()
    // register handlers to mux
    mux.HandleFunc(«/courses», getHandler).Methods(«GET»)
    mux.HandleFunc(«/courses», postHandler).Methods(«POST»)

Использование пакета github.com/gorilla/mux

Пакет gorilla/muxшироко используется на протяжении многих лет. Однако github.com/gorilla/muxрепозиторий был заархивирован владельцем в декабре 2022 года. Поэтому нам нужна (лучшая) альтернатива, и мы ginможем нам помочь. Ginпредставляет собой веб-фреймворк на основе Go для создания веб-приложений; он также упрощает и улучшает многие связанные аспекты.

В оставшейся части этого блога мы отойдем от стандартного net/httpпакета и будем использовать gin. Однако мы продолжим сравнение, ginчтобы net/httpподчеркнуть, насколько это упрощает процесс разработки.

Читайте также:  Очередь (queue) в C++: реализация и использование

Шаг 2а: GETвсе курсы с использованиемgin

Прежде чем двигаться дальше, давайте обсудим, как мы можем добиться той же функциональности, что и на шаге 2, используя файлы gin. Обновленный код, за исключением структуры данных и coursesсреза для краткости, выглядит следующим образом:

package main
import (
    «net/http»
    «github.com/gin-gonic/gin»
)
/* removed struct declaration and courses instantiation for brevity */
func main() {
    grouter := gin.Default()
    grouter.GET(«/courses», getcourses)
    grouter.Run() //listens on 8080, by default
}
func getcourses(c *gin.Context) {
    c.IndentedJSON(http.StatusOK, courses)
}

ПОЛУЧИТЬ все курсы, используя джин

Мы будем восхищаться возможностями по ginмере продвижения и построения других маршрутов. Однако с самого начала мы можем заметить, насколько чистый наш код по сравнению с более ранней версией. В основной функции выше мы инициализируем маршрутизатор и указываем функцию-обработчик. Функция-обработчик использует переменную-указатель типа gin.Context, представляющего сведения о запросе и другую связанную информацию.

Вызов c.IndentedJSON(http.StatusOK, courses)неявно сериализует нашу структуру в JSON и записывает версию с отступом обратно клиенту вместе с кодом состояния 200 OK. В качестве альтернативы мы можем использовать c.JSONдля более компактного ответа JSON. Теперь мы готовы реализовать и другие конечные точки.

Шаг 3: GETдетали конкретного курса

Наша функция-обработчик getcoursesв настоящее время возвращает клиенту полный список курсов. Что, если мы хотим получить информацию о конкретном курсе, указав его идентификатор? Хотя у нас есть некоторая идея, основанная на том, что было сказано выше, о том, как это можно сделать, давайте сначала вернемся к части того, что мы планировали при определении конечных точек.

HTTP- запрос Имя конечной точки Описание
GET /courses/:id Параметр ID можно использовать для получения информации о конкретном курсе.

Имя конечной точки /courses/:idвключает idпараметр. URL-адрес для доступа к этой конечной точке будет отличаться от того, который используется для доступа к /coursesконечной точке. Как вы, наверное, догадались, сначала нам нужно обновить нашу маршрутизацию в функции main и ввести новый маршрут для этого случая.

func main() {
    grouter := gin.Default()
    grouter.GET(«/courses», getcourses)
    //we have added a new route below
    grouter.GET(«/courses/:id», getSpecificCourse)
    grouter.Run() //listens on 8080, by default
}

Новое определение маршрута в основной функции

Из приведенного выше кода видно, что мы добавили новое определение маршрута. Как только пользователь получает доступ к конечной точке для определенного курса, он перенаправляется к getSpecificCourseфункции. Здесь мы перебираем coursesструктуру, используя rangeключевое слово, и отправляем ответ, если курс IDсоответствует курсу, отправленному в запросе, как показано ниже:

func getSpecificCourse(c *gin.Context) {
    //ID passed as a path parameter can then be retrieved
    requestedId := c.Param(«id»)
    for _, course := range courses {
        if course.ID == requestedId {
            c.IndentedJSON(http.StatusOK, course)
            return
        }
    }
}

ПОЛУЧИТЬ подробную информацию о конкретном курсе

Обратите внимание, что мы используем специальный синтаксис для указания этого маршрута, /courses/:idи любой idпереданный в качестве параметра пути можно получить с помощью c.Param(«id«).

Шаг 4. Обработка POSTдобавления нового курса

Чтобы добавить новый курс в нашу коллекцию, мы будем использовать ту же /coursesконечную точку. На этот раз мы будем использовать POSTметод с этой конечной точкой. Давайте на минутку разберемся, как это можно сделать. Это не сложно, и вы, должно быть, уже разобрались с процессом, которому нужно следовать. Первым шагом является добавление нового определения маршрута в mainфункцию:

func main() {
    grouter := gin.Default()
    grouter.GET(«/courses», getcourses)
    grouter.GET(«/courses/:id», getSpecificCourse)
    //we have added a new route below
    grouter.POST(«/courses», addCourse)
    grouter.Run()
}

Новое определение маршрута в основной функции

В приведенном выше коде мы обновляем нашу mainфункцию для обработки POSTзапросов. Мы можем заметить добавление метода POSTс обработчиком маршрута. Далее нам нужно обработать запрос, как только он достигнет функции обработчика, addCourse.

Функция обработчика, опять же, относительно проста. Основная часть — это вызов функции BindJSON, которая десериализует информацию о курсе, отправленную с запросом, в формате JSON в переменную структуры. Конечно, процесс может завершиться ошибкой из-за неправильно сформированных запросов; мы уже рассмотрели и этот случай. Полный код функции addCourse показан ниже:

func addCourse(c *gin.Context) {
    var courseToAdd course
    err := c.BindJSON(&courseToAdd)
    if err != nil {
      c.IndentedJSON(http.StatusBadRequest, gin.H{«message»: «malformed request!»})
    }
    courses = append(courses, courseToAdd)
    c.IndentedJSON(http.StatusOK, courses)
}

Обработка POST для добавления нового курса

Читайте также:  Массив указателей C++

Прежде чем мы двинемся дальше, подумайте о том, как это можно сделать с помощью стандартного net/httpпакета и как это ginупрощает и облегчает процесс.

Шаг 5: PUTобновленная версия

Мы можем использовать HTTP PUTметод для обновления ресурса (и PATCHдля его частичного обновления). Как это может быть сделано? См. приведенный ниже код, чтобы понять, что нужно сделать. Если вы все еще не уверены, краткое описание следующей задачи поможет вам полностью ее понять.

func updateCourse(c *gin.Context) {
    courseIdtoUpdate := c.Param(«id»)
    var updatedCourse course
    c.BindJSON(&updatedCourse)
    for index, course := range courses {
        if course.ID == courseIdtoUpdate {
            courses[index] = updatedCourse
            c.IndentedJSON(http.StatusOK, courses)
            return
        }
    }
    c.IndentedJSON(http.StatusNotFound, gin.H{«message»: «course not found!»})
}

ПОСТАВЬТЕ обновленную версию

Шаг 6: DELETEконкретный курс

Давайте завершим этот блог обсуждением того, как удалить курс. Использование gin делает это простым. Мы указываем новый обработчик в DELETEкачестве HTTPметода и используем idс динамическим маршрутом. Затем мы анализируем его внутри обработчика и удаляем из среза.

Вот полный код:

package main
//we are using net/http and gin
import (
    «net/http»
    «github.com/gin-gonic/gin»
)
//data structure to hold course information
type course struct {
    ID    string `json:»id»`
    Title string `json:»title»`
}
//initializing some courses
var courses = []course{
    {«100», «Grokking Modern System Design «},
    {«101», «CloudLab: WebSockets-based Chat Application using API Gateway»},
}
//main function contains routes for performing different operations
func main() {
    grouter := gin.Default()
    grouter.GET(«/courses», getcourses)
    grouter.GET(«/courses/:id», getSpecificCourse)
    grouter.POST(«/courses», addCourse)
    grouter.PUT(«/courses/:id», updateCourse)
    grouter.DELETE(«/courses/:id», deleteCourse)
    grouter.Run()
}
//GET all courses using gin
func getcourses(c *gin.Context) {
    c.IndentedJSON(http.StatusOK, courses)
}
//GET details of a specific course
func getSpecificCourse(c *gin.Context) {
    requestedId := c.Param(«id»)
    for _, course := range courses {
        if course.ID == requestedId {
            c.IndentedJSON(http.StatusOK, course)
            return
        }
    }
}
//Handling POST to add a new course
func addCourse(c *gin.Context) {
    var courseToAdd course
    err := c.BindJSON(&courseToAdd)
    if err != nil {
        c.IndentedJSON(http.StatusBadRequest, gin.H{«message»: «malformed request!»})
    }
    courses = append(courses, courseToAdd)
    c.IndentedJSON(http.StatusOK, courses)
}
func inefficientRemoval(srcSlice []course, index int) []course {
    return append(srcSlice[:index], srcSlice[index+1:]…)
}
//DELETE a specific course
func deleteCourse(c *gin.Context) {
    requestedId := c.Param(«id»)
    courseID := -1
    for index, course := range courses {
        if course.ID == requestedId {
            courseID = index
            break
        }
    }
    if courseID != -1 {
        courses = inefficientRemoval(courses, courseID)
        c.IndentedJSON(http.StatusOK, courses)
        return
    }
    c.IndentedJSON(http.StatusNotFound, gin.H{«message»: «course not found!»})
}
//PUT an updated version
func updateCourse(c *gin.Context) {
    courseIdtoUpdate := c.Param(«id»)
    var updatedCourse course
    c.BindJSON(&updatedCourse)
    for index, course := range courses {
        if course.ID == courseIdtoUpdate {
            courses[index] = updatedCourse
            c.IndentedJSON(http.StatusOK, courses)
            return
        }
    }
    c.IndentedJSON(http.StatusNotFound, gin.H{«message»: «course not found!»})
}

Заключение

В этом блоге мы предоставили пошаговое руководство по созданию RESTful API. Мы рассмотрели пример создания API для управления курсами и использовали Go в качестве языка программирования. Однако концепции, представленные в этом блоге, могут быть применены к другим языкам программирования и сопоставлены с другими вариантами использования.

Этот блог посвящен RESTful API; однако другие стили архитектуры API, включая GraphQL и gRPC, имеют свои сильные стороны и варианты использования. Мы рекомендуем вам изучить их подробнее.

Оцените статью
bestprogrammer.ru
Добавить комментарий