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;