Использование 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 массива. Достаточно просто.

Протестируйте getAPI, используя 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"
)

Несколько вещей, на которые стоит обратить внимание -

  1. Мы не манипулируем массивом напрямую, начиная с самого массива, а создаем новый массив каждый раз, когда в этой строке происходят изменения todos = append(todos[0:i], todos[i+1:]...). Не эффективно, но работает. Вместо этого мы могли бы напрямую использовать указатель и обновлять массив.
  2. Мы используем удобный ярлык, предоставляемый 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"})
}