gofiber を使用した API 開発

# gofiber とは

https://docs.gofiber.io/

> Fiber is an Express inspired web framework built on top of Fasthttp, the fastest HTTP engine for Go. Designed to ease things up for fast development with zero memory allocation and performance in mind.


gofiber は Go のための Web フレームワークです。
Node.js のための Web フレームワークである express にインスパイアされているようです。

# Installation

[Installation](https://docs.gofiber.io/#installation) に従い gofiber をインストールします。

```shell
go get github.com/gofiber/fiber/v2
```

# Server の起動

```main.go
package main

import (
	"github.com/gofiber/fiber/v2"
)

func main() {
	app := fiber.New()

	if err := app.Listen(":3000"); err != nil {
		println(err.Error())
	}
}
```

```shell
go run ./main.go 
```

起動が成功すると以下のログが表示されます。

```shell
 ┌───────────────────────────────────────────────────┐ 
 │                   Fiber v2.52.5                   │ 
 │               http://127.0.0.1:3000               │ 
 │       (bound on host 0.0.0.0 and port 3000)       │ 
 │                                                   │ 
 │ Handlers ............. 0  Processes ........... 1 │ 
 │ Prefork ....... Disabled  PID ............. 31623 │ 
 └───────────────────────────────────────────────────┘ 
 ```

# Routing

https://docs.gofiber.io/guide/routing

Fiber の routing 機能では、HTTP request method と path を handler に対応させることができます。

## 試してみた

いくつかのリクエストを実装してみて、routing がどのように機能するのか試してみました。

### POST request

`POST /tasks` を `CreateTaskHandler` method にバインドする実装です。

```main.go
package main

import (
	"github.com/gofiber/fiber/v2"
)

func main() {
	app := fiber.New()

	taskHandler := NewTaskHandler()

	app.Post("/tasks", taskHandler.CreateTaskHandler)

	if err := app.Listen(":3000"); err != nil {
		println(err.Error())
	}
}

type TaskHandler struct{}

type CreateTaskRequest struct {
	Name string `json:"name"`
}

type CreateTaskResponse struct {
	Name string `json:"name"`
}

func (h *TaskHandler) CreateTaskHandler(c *fiber.Ctx) error {
	request := new(CreateTaskRequest)

	if err := c.BodyParser(request); nil != err {
		return err
	}

	response := CreateTaskResponse{
		Name: request.Name,
	}

	if err := c.Status(fiber.StatusCreated).JSON(response); nil != err {
		return err
	}

	return nil
}

func NewTaskHandler() *TaskHandler {
	return &TaskHandler{}
}
```

[Ctx API の BodyParser メソッド](https://docs.gofiber.io/api/ctx#bodyparser) を使用することで Request Body をパースできます。

#### Request

```shell
curl -X POST localhost:3000/tasks \
    -H "Content-Type: application/json" \
    --data "{\"name\": \"Test name\"}"
```

#### Server

```shell
Test name
```

#### Response

```shell
{"name":"Test name"}
```

### GET request (1)

`GET /tasks` request を `ListTasksHandler` method にバインドする実装です。

```main.go
package main

import (
	"github.com/gofiber/fiber/v2"
)

func main() {
	app := fiber.New()

	taskHandler := NewTaskHandler()

	app.Get("/tasks", taskHandler.ListTasksHandler)

	if err := app.Listen(":3000"); err != nil {
		println(err.Error())
	}
}

type TaskHandler struct{}

type ListTaskResponse struct {
	Name string `json:"name"`
}

func (h *TaskHandler) ListTasksHandler(c *fiber.Ctx) error {
	response := make([]ListTaskResponse, 0, 2)
	response = append(response, ListTaskResponse{
		Name: "Test Name 1",
	})
	response = append(response, ListTaskResponse{
		Name: "Test Name 2",
	})

	if err := c.Status(fiber.StatusOK).JSON(response); nil != err {
		return err
	}

	return nil
}

func NewTaskHandler() *TaskHandler {
	return &TaskHandler{}
}
```

Go の slice は JSON の配列として返されます。

#### Request

```shell
curl -X GET localhost:3000/tasks \
    -H "Content-Type: application/json"
```

#### Response

```shell
[{"name":"Test Name 1"},{"name":"Test Name 2"}]
```

### GET request (2)

`GET /tasks/:id` request を `GetTaskHandler` method にバインドする実装です。

```main.go
package main

import (
	"github.com/gofiber/fiber/v2"
)

func main() {
	app := fiber.New()

	taskHandler := NewTaskHandler()

	app.Get("/tasks/:id", taskHandler.GetTaskHandler)

	if err := app.Listen(":3000"); err != nil {
		println(err.Error())
	}
}

type TaskHandler struct{}

type Task struct {
	ID   int
	Name string
}

type GetTaskResponse struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

func (h *TaskHandler) GetTaskHandler(c *fiber.Ctx) error {
	param := struct {
		ID int `params:"id"`
	}{}

	if err := c.ParamsParser(&param); err != nil {
		return err
	}

	tasks := make([]Task, 0, 2)
	tasks = append(tasks, Task{
		ID:   1,
		Name: "Test Name 1",
	})
	tasks = append(tasks, Task{
		ID:   2,
		Name: "Test Name 2",
	})

	var response GetTaskResponse
	for _, task := range tasks {
		if task.ID == param.ID {
			response = GetTaskResponse{
				ID:   task.ID,
				Name: task.Name,
			}
		}
	}

	if err := c.Status(fiber.StatusOK).JSON(response); nil != err {
		return err
	}

	return nil
}

func NewTaskHandler() *TaskHandler {
	return &TaskHandler{}
}
```

[Ctx API の paramsparser method](https://docs.gofiber.io/api/ctx#paramsparser) を使用することで path parameter の値を取得できます。

#### Request

```shell
curl -X GET localhost:3000/tasks/2 \
    -H "Content-Type: application/json"
```

#### Response

```shell
{"id":2,"name":"Test Name 2"}
```

### PUT request

`PUT /tasks/:id` request を `UpdateTaskHandler` method にバインドする実装です。

```main.go
package main

import (
	"github.com/gofiber/fiber/v2"
)

func main() {
	app := fiber.New()

	taskHandler := NewTaskHandler()

	app.Put("/tasks/:id", taskHandler.UpdateTaskHandler)

	if err := app.Listen(":3000"); err != nil {
		println(err.Error())
	}
}

type TaskHandler struct{}

type Task struct {
	ID   int
	Name string
}

type UpdateTaskRequest struct {
	Name string `json:"name"`
}

type UpdateTaskResponse struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

func (h *TaskHandler) UpdateTaskHandler(c *fiber.Ctx) error {
	request := new(UpdateTaskRequest)

	if err := c.BodyParser(request); nil != err {
		return err
	}

	param := struct {
		ID int `params:"id"`
	}{}

	if err := c.ParamsParser(&param); err != nil {
		return err
	}

	tasks := make([]Task, 0, 2)
	tasks = append(tasks, Task{
		ID:   1,
		Name: "Test Name 1",
	})
	tasks = append(tasks, Task{
		ID:   2,
		Name: "Test Name 2",
	})

	var response UpdateTaskResponse
	for _, task := range tasks {
		if task.ID == param.ID {
			task.Name = request.Name

			response = UpdateTaskResponse{
				ID:   task.ID,
				Name: task.Name,
			}
		}
	}

	if err := c.Status(fiber.StatusOK).JSON(response); nil != err {
		return err
	}

	return nil
}

func NewTaskHandler() *TaskHandler {
	return &TaskHandler{}
}
```

#### Request

```shell
curl -X PUT localhost:3000/tasks/2 \
    -H "Content-Type: application/json" \
    --data "{\"name\": \"Test name 3\"}"
```

#### Response

```shell
{"id":2,"name":"Test name 3"}
```

### DELETE request

`DELETE /tasks/:id` request を `DeleteTaskHandler` method にバインドする実装です。

```main.go
package main

import (
	"github.com/gofiber/fiber/v2"
)

func main() {
	app := fiber.New()

	taskHandler := NewTaskHandler()

	app.Delete("/tasks/:id", taskHandler.DeleteTaskHandler)

	if err := app.Listen(":3000"); err != nil {
		println(err.Error())
	}
}

type TaskHandler struct{}

func (h *TaskHandler) DeleteTaskHandler(c *fiber.Ctx) error {
	param := struct {
		ID int `params:"id"`
	}{}

	if err := c.ParamsParser(&param); err != nil {
		return err
	}

	println(param.ID)

	if err := c.Status(fiber.StatusNoContent).JSON(nil); nil != err {
		return err
	}

	return nil
}

func NewTaskHandler() *TaskHandler {
	return &TaskHandler{}
}
```

#### Request

```shell
curl -X DELETE localhost:3000/tasks/5 \
    -H "Content-Type: application/json"
```

#### Server

```shell
5
```

# 最後に

今回は routing や Ctx API を主に触りましたが、特に routing 機能は直感的で扱いやすく感じました。また gofiber は公式ドキュメントが充実しており、サンプルを見ながら手を動かすことができる点が良いと思いました。
実際の API ではデータベースとのやり取りがあったりしますが、今回の例には含めていないためイメージがわきづらいものとなってしまったかもしれません。申し訳ないです。