Использование Fiber в Golang
В сочетании с простотой Go, легкостью, легкостью асинхронного программирования и развертывания Fiber представляет собой мощный (хотя и простой) инструмент для быстрой разработки веб-приложений.
Fiber framework — это фреймворк, вдохновленный ExpressJS.
- разработчикам NodeJS легко освоить
- быстрый (очень быстрый — см. тесты )
- легко разработать
- гибкий
В этом посте давайте рассмотрим пример разработки простых API-интерфейсов CRUD в Fiber v2. Мы не будем использовать ORM или базу данных, чтобы сосредоточиться на Fiber и Golang.
Структура проекта
Создайте папку на своем компьютере и откройте ее в VSCode.
Создайте новый файл server.go
. Код следующий:
package main import "fmt" func main() { fmt.Println("hello world") }
Откройте терминал в VSCode ( Ctrl + ~
) и запустите программу.
go run server.go
Вы должны увидеть выходное сообщение, если все работает нормально.
Начало использования Fiber
Введите приведенную ниже команду в терминале VSCode, чтобы установить Fiber.
go get github.com/gofiber/fiber/v2
Теперь мы начнем кодировать API в формате server.go
.
package main import ( "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/logger" ) type Todo struct { ID int `json:"id"` Name string `json:"name"` Completed bool `json:"completed"` } var todos = []Todo{ {ID: 1, Name: "abc", Completed: false}, {ID: 2, Name: "def", Completed: true}, } func main() { app := fiber.New() app.Use(logger.New()) app.Get("/", func(c *fiber.Ctx) error { return c.SendString("Hello, World!") }) app.Listen(":5000") }
Здесь мы сделали несколько простых вещей:
- определил базовую структуру для нашей задачи, используя
type Todo struct {}
. Мы также предоставили эквиваленты полей в формате JSON. - создал простой массив типа
Todo
-var todos = []Todo{{ID: 1, Name: "abc", Completed: false},}
- инициированное волокно с
app := fiber.New()
- включена регистрация входных запросов в одну строку
app.Use(logger.New())
- включил простой ответ в корне, используя
app.Get("/", ...)
Запустите программу еще раз, используя go run server.go
.
Используйте клиент REST (например, Insomnia ), вызовите свой API, http://localhost:5000/
чтобы увидеть hello world
ответ.
Вы можете увидеть сходство с сервером Express в простой программе - дальше будет больше.
На этом этапе вы можете заметить, что любое внесенное вами изменение потребует перезагрузки сервера. Есть несколько способов решения этой проблемы, включая использование нашего дорогого nodemon
. Однако давайте придерживаться решения Go — мы будем использовать пакет под названием air для отслеживания изменений в папке нашего проекта и перезапускать сервер всякий раз, когда происходят изменения.
В терминале VSCode введите -
https://github.com/cosmtrek/air
Вы можете остановить свой собственный сервер Go и просто ввести —
air
Приведенная выше команда запустит ваш сервер и автоматически перезапустит его при появлении изменений.
API CRUD в Fiber
Давайте закодируем API в нашей программе.
GET запрос
Введите строку ниже в main
функцию перед app.Listen(":5000")
строкой.
app.Get("/todo", getTodo)
Создайте новую функцию ниже main
.
func getTodo(c *fiber.Ctx) error { return c.Status(fiber.StatusOK).JSON(todos) }
Функция (более или менее) не требует пояснений. Указатель c
— это контекст волокна, который внедряется в нашу функцию с помощью инфраструктуры волокна. Мы просто возвращаем данные ok
нашего todos
массива. Достаточно просто.
Протестируйте get
API, используя URL-адрес и метод клиента REST. Вы увидите ответ в формате JSON ниже.http://localhost:5000/todo GET
[ { "id": 1, "name": "abc", "completed": false }, { "id": 2, "name": "def1", "completed": false } ]
POST запрос
Введите новую строку после запроса GET в файле server.go
.
app.Post("/todo", postTodo)
Как и раньше, создайте новую функцию под названием postTodo
-
func postTodo(c *fiber.Ctx) error { type request struct { Name string `json:"name"` Completed bool `json:"completed"` } var body request err := c.BodyParser(&body) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "Cannot parse JSON", }) } todo := Todo{ ID: len(todos) + 1, Name: body.Name, Completed: body.Completed, } todos = append(todos, todo) return c.Status(fiber.StatusCreated).JSON(todo) }
Мы добились немалого, но ничего необычного -
- указал, как будет выглядеть наш входной JSON (
request
struct) - включено промежуточное программное обеспечение синтаксического анализатора тела для получения входных данных JSON
- извлек входящие данные, используя
todo
переменную - добавлено
todo
в нашtodos
массив - вернул сообщение об успехе обратно
Приведенный ниже блок кода отвечает за проверку ошибок (в нашем случае это обработка ошибок синтаксического анализа) —
err := c.BodyParser(&body) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "Cannot parse JSON", }) }
Этот тип обработки ошибок типичен для программы Go. Проверки ошибок обрабатываются оператором if
, который проверяет error
результаты предыдущего оператора.
Создайте запрос POST в вашем клиенте REST — http://localhost:5000/todo
. Введите следующий JSON в качестве входных данных:
{ "name": "xyz", "completed": true }
Отправьте запрос и получите тот же ответ. Выполните запрос GET с тем же URL-адресом, и вы увидите дополнительную todo
запись в ответе.
Конечно, мы имеем дело только с переменной, которая остается в памяти. Изменения исчезнут после выключения или перезапуска сервера.
Примечание: логика генерации идентификаторов ненадежна. Мы использовали длину массива и увеличили ее на 1. Следовательно, удаление записи может привести к дублированию идентификаторов.
DELETE запрос
Вы уже знаете упражнение.
Представляем новый сервис REST в main
..
app.Delete("/todo/:id", deleteTodo)
Создайте новую функцию.
func deleteTodo(c *fiber.Ctx) error { paramID := c.Params("id") id, err := strconv.Atoi(paramID) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "Invalid id.", }) } for i, todo := range todos { if todo.ID == id { todos = append(todos[0:i], todos[i+1:]...) return c.Status(fiber.StatusOK).JSON(todo) } } return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Record not found"}) }
Мы использовали новую функцию для преобразования типов строк. Включите пакет в import
заявление.
import ( "strconv" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/logger" )
Несколько вещей, на которые стоит обратить внимание -
- Мы не манипулируем массивом напрямую, начиная с самого массива, а создаем новый массив каждый раз, когда в этой строке происходят изменения
todos = append(todos[0:i], todos[i+1:]...)
. Не эффективно, но работает. Вместо этого мы могли бы напрямую использовать указатель и обновлять массив. - Мы используем удобный ярлык, предоставляемый Fiber, для создания ответа в формате JSON на ходу —
fiber.Map{"error": "Record not found"}
PATH запрос
PATH похож на delete
- за исключением того, что мы изменяем элемент, а не удаляем его.
Включите вызов новой функции в main
.
app.Patch("/todo/:id", patchTodo)
Создайте функцию.
func patchTodo(c *fiber.Ctx) error { type request struct { Name string `json:"name"` Completed bool `json:"completed"` } var body request err := c.BodyParser(&body) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "Cannot parse JSON", }) } paramID := c.Params("id") id, err := strconv.Atoi(paramID) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "Invalid id.", }) } for i, todo := range todos { if todo.ID == id { todos[i] = Todo{ ID: id, Name: body.Name, Completed: body.Completed, } return c.Status(fiber.StatusOK).JSON(todos[i]) } } return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Record not found"}) }
Весь код в одном месте
Мы создали программу, использующую Fiber для выполнения операций CRUD, но с локальной переменной. С помощью еще пары пакетов и нескольких строк кода мы могли бы подключиться к БД и перейти к более реальной программе.
Код ниже представляет конечное состояние.
package main import ( "strconv" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/logger" ) // Todo struct! type Todo struct { ID int `json:"id"` Name string `json:"name"` Completed bool `json:"completed"` } var todos = []Todo{ {ID: 1, Name: "abc", Completed: false}, {ID: 2, Name: "def", Completed: true}, } func main() { app := fiber.New() app.Use(logger.New()) app.Get("/", func(c *fiber.Ctx) error { return c.SendString("Hello, World!") }) app.Get("/todo", getTodo) app.Post("/todo", postTodo) app.Get("/todo/:id", getSingleTodo) app.Delete("/todo/:id", deleteTodo) app.Patch("/todo/:id", patchTodo) app.Listen(":5000") } func getTodo(c *fiber.Ctx) error { return c.Status(fiber.StatusOK).JSON(todos) } func getSingleTodo(c *fiber.Ctx) error { paramID := c.Params("id") id, err := strconv.Atoi(paramID) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "ID invalid.", }) } for _, todo := range todos { if todo.ID == id { return c.Status(fiber.StatusFound).JSON(todo) } } return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Record not found"}) } func deleteTodo(c *fiber.Ctx) error { paramID := c.Params("id") id, err := strconv.Atoi(paramID) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "Invalid id.", }) } for i, todo := range todos { if todo.ID == id { todos = append(todos[0:i], todos[i+1:]...) return c.Status(fiber.StatusOK).JSON(todo) } } return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Record not found"}) } func postTodo(c *fiber.Ctx) error { type request struct { Name string `json:"name"` Completed bool `json:"completed"` } var body request err := c.BodyParser(&body) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "Cannot parse JSON", }) } todo := Todo{ ID: len(todos) + 1, Name: body.Name, Completed: body.Completed, } todos = append(todos, todo) return c.Status(fiber.StatusCreated).JSON(todo) } func patchTodo(c *fiber.Ctx) error { type request struct { Name string `json:"name"` Completed bool `json:"completed"` } var body request err := c.BodyParser(&body) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "Cannot parse JSON", }) } paramID := c.Params("id") id, err := strconv.Atoi(paramID) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "Invalid id.", }) } for i, todo := range todos { if todo.ID == id { todos[i] = Todo{ ID: id, Name: body.Name, Completed: body.Completed, } return c.Status(fiber.StatusOK).JSON(todos[i]) } } return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Record not found"}) }