Go + Reactでアプリを作成してみた【Todo】

## technology used

- Go - Server
- Fiber - Go web server
- Vite - Client
- Mantine - React component library
- TypeScript - Static types

## Serverを作成する

```NOTES.md
## Create a Server folder

mkdir Server

## initialize Go app

go mod init github.com/tomdoestech/go-react-todo

## Install Riber v2

go get -u github.com/gofiber/fiver/v2

## Create client app with Vite

yarn create vite client -- --template react-ts

## Install dependencies

yarn add @mantine/hooks @mantine/core swr @primer/octicons-react
```
①serverフォルダを作成して、initialize Go appする

②server/main.goを作成する

```server/main.go
package main

import (
	"fmt"
	"log"

	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/cors"
)

type Todo struct {
	ID int `json:"id"`
	Title string `json:"title"`
	Done bool `json:"done"`
	Body string `json:"body"`
}

func main(){
	fmt.Print("Hello world")

	app := fiber.New()

	app.Use(cors.New(cors.Config{
		AllowOrigins: "http://localhost:3000",
		AllowHeaders: "Origin, Content-Type, Accept",
	}))

	todos := []Todo{}

	app.Get("/healthchecl", func(c *fiber.Ctx) error {
		return c.SendString("OK")
	})

	app.Post("/api/todos", func(c *fiber.Ctx) error {
		todo := &Todo{}

		if err := c.BodyParser(todo); err != nil {
			return err
		}
		todo.ID = len(todos) + 1
		todos = append(todos, *todo)
		return c.JSON(todos)
	})
	app.Patch("/api/todos/:id/done", func(c *fiber.Ctx) error {
		id, err := c.ParamsInt("id")

		if err != nil {
			return c.Status(401).SendString("Invalid id")
		}

		for i, t := range todos {
			if t.ID == id {
				todos[i].Done = true
				break
			}
		}
		return c.JSON(todos)
	})

	app.Get("/api/todos", func(c *fiber.Ctx) error {
		return c.JSON(todos)
	})

	log.Fatal(app.Listen(":4000"))
}
```
③cd serverとgo run main.goを行う

④postman でエンドポイントを試す

⑤client/src/App.tsxを編集する

```client/src/App.tsx
import { Box, List, ThemeIcon } from '@mantine/core';
import { CheckCircleFillIcon } from '@primer/octicons-react';
import useSWR from 'swr';
import './App.css';
import AddTodo from './components/AddTodo';

export interface Todo {
  id: number;
  title: string;
  body: string;
  done: boolean;
}

export const ENDPOINT = 'http://localhost:4000';

const fetcher = (url: string) =>
  fetch(`${ENDPOINT}/${url}`).then((r) => r.json());

function App() {
  const { data, mutate } = useSWR<Todo[]>('api/todos', fetcher);

  async function markTodoAdDone(id: number) {
    const updated = await fetch(`${ENDPOINT}/api/todo/${id}/done`, {
      method: 'PATCH',
    }).then((r) => r.json());
    mutate(updated);
  }

  return (
    <Box
      style={{
        padding: '2rem',
        width: '100%', // また、'windth' は 'width' のタイプミスですので、これも修正しました。
        maxWidth: '40rem',
        margin: '0 auto',
      }}
    >
      <List spacing='xs' size='sm' mb={12} center>
        {data?.map((todo) => {
          return (
            <List.Item
              onClick={() => markTodoAdDone(todo.id)}
              key={`todo_list__${todo.id}`}
            >
              icon=
              {todo.done ? (
                <ThemeIcon color='teal' size={24} radius='xl'>
                  <CheckCircleFillIcon size={20} />
                </ThemeIcon>
              ) : (
                <ThemeIcon color='gray' size={24} radius='xl'>
                  <CheckCircleFillIcon size={20} />
                </ThemeIcon>
              )}
              {todo.title}
            </List.Item>
          );
        })}
      </List>
      <AddTodo mutate={mutate} />
    </Box>
  );
}

export default App;
```

⑥src/components/AddTodo.tsxを作成する

```src/components/AddTodo.tsx
import { useState } from 'react';
import { useForm } from '@mantine/form';
import { Button, Modal, Group, TextInput, Textarea } from '@mantine/core';
import { ENDPOINT } from '../App';
import { KeyedMutator } from 'swr';

function AddTodo({ mutate }: { mutate: KeyedMutator<Todo[]> }) {
  const [open, setOpen] = useState(false);

  const form = useForm({
    initalValues: {
      title: '',
      body: '',
    },
  });

  async function createTodo(values: { title: string; body: string }) {
    const updated = await fetch(`${ENDPOINT}/api/todos`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(values),
    }).then((r) => r.json());

    mutate(updated);
    form.reset();
    setOpen(false);
  }

  return (
    <>
      <Modal opened={open} onClose={() => setOpen(false)} title='Create todo'>
        <form onSubmit={form.onSubmit(createTodo)}>
          <TextInput
            required
            mb={12}
            label='Todo'
            placeholder='What do you want to do?'
            {...form.getInputProps('title')}
          />
          <Textarea
            required
            mb={12}
            label='Body'
            placeholder='Tell me more...'
            {...form.getInputProps('body')}
          />

          <Button type='submit'>Create todo</Button>
        </form>
      </Modal>
      <Group>
        <Button fullWidth mb={12} onClick={() => setOpen(true)}>
          ADD TODO
        </Button>
      </Group>
    </>
  );
}

export default AddTodo;
```




## 参考サイト
[Build A Go REST API, React.js & TypeScript Todo Application](https://www.youtube.com/watch?v=QevhhM_QfbM&list=WL&index=6&t=1745s)