├── NOTES.md ├── README.md ├── client ├── .gitignore ├── index.html ├── package.json ├── src │ ├── App.css │ ├── App.tsx │ ├── components │ │ └── AddTodo.tsx │ ├── favicon.svg │ ├── index.css │ ├── logo.svg │ ├── main.tsx │ └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── yarn.lock └── server ├── go.mod ├── go.sum └── main.go /NOTES.md: -------------------------------------------------------------------------------- 1 | ## Initialize Go app 2 | go mod init github.com/tomdoestech/go-react-todo 3 | 4 | ## Install Fiber v2 5 | go get -u github.com/gofiber/fiber/v2 6 | 7 | ## Create client app with Vite 8 | yarn create vite client -- --template react-ts 9 | 10 | ## Install dependencies 11 | yarn add @mantine/hooks @mantine/core swr @primer/octicons-react -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Build Todo Application with Go & React 2 | 3 | ## What are we using? 4 | * [Go](https://go.dev/) - Server 5 | * [Fiber](https://github.com/gofiber/fiber) - Go web server 6 | * [Vite](https://vitejs.dev/) - Client 7 | * [Mantine](https://mantine.dev/) - React component library 8 | * [TypeScript](https://www.typescriptlang.org/) - Static types 9 | 10 | ## What will you learn 11 | * How to make a basic Go REST API 12 | * How to make a todo UI 13 | * React with TypeScript 14 | * Basic Mantine usage -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "tsc && vite build", 8 | "preview": "vite preview" 9 | }, 10 | "dependencies": { 11 | "@mantine/core": "^3.6.14", 12 | "@mantine/hooks": "^3.6.14", 13 | "@primer/octicons-react": "^17.0.0", 14 | "react": "^17.0.2", 15 | "react-dom": "^17.0.2", 16 | "swr": "^1.2.2" 17 | }, 18 | "devDependencies": { 19 | "@types/react": "^17.0.33", 20 | "@types/react-dom": "^17.0.10", 21 | "@vitejs/plugin-react": "^1.0.7", 22 | "typescript": "^4.5.4", 23 | "vite": "^2.8.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /client/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | padding: 2rem; 3 | display: flex; 4 | flex-direction: column; 5 | align-items: center; 6 | } 7 | -------------------------------------------------------------------------------- /client/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Box, List, ThemeIcon } from "@mantine/core"; 2 | import { CheckCircleFillIcon } from "@primer/octicons-react"; 3 | import useSWR from "swr"; 4 | import "./App.css"; 5 | import AddTodo from "./components/AddTodo"; 6 | 7 | export interface Todo { 8 | id: number; 9 | title: string; 10 | body: string; 11 | done: boolean; 12 | } 13 | 14 | export const ENDPOINT = "http://localhost:4000"; 15 | 16 | const fetcher = (url: string) => 17 | fetch(`${ENDPOINT}/${url}`).then((r) => r.json()); 18 | 19 | function App() { 20 | const { data, mutate } = useSWR("api/todos", fetcher); 21 | 22 | async function markTodoAdDone(id: number) { 23 | const updated = await fetch(`${ENDPOINT}/api/todosn/${id}/done`, { 24 | method: "PATCH", 25 | }).then((r) => r.json()); 26 | 27 | mutate(updated); 28 | } 29 | 30 | return ( 31 | ({ 33 | padding: "2rem", 34 | width: "100%", 35 | maxWidth: "40rem", 36 | margin: "0 auto", 37 | })} 38 | > 39 | 40 | {data?.map((todo) => { 41 | return ( 42 | markTodoAdDone(todo.id)} 44 | key={`todo_list__${todo.id}`} 45 | icon={ 46 | todo.done ? ( 47 | 48 | 49 | 50 | ) : ( 51 | 52 | 53 | 54 | ) 55 | } 56 | > 57 | {todo.title} 58 | 59 | ); 60 | })} 61 | 62 | 63 | 64 | 65 | ); 66 | } 67 | 68 | export default App; 69 | -------------------------------------------------------------------------------- /client/src/components/AddTodo.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { useForm } from "@mantine/hooks"; 3 | import { Button, Modal, Group, TextInput, Textarea } from "@mantine/core"; 4 | import { ENDPOINT, Todo } from "../App"; 5 | import { KeyedMutator } from "swr"; 6 | 7 | function AddTodo({ mutate }: { mutate: KeyedMutator }) { 8 | const [open, setOpen] = useState(false); 9 | 10 | const form = useForm({ 11 | initialValues: { 12 | title: "", 13 | body: "", 14 | }, 15 | }); 16 | 17 | async function createTodo(values: { title: string; body: string }) { 18 | const updated = await fetch(`${ENDPOINT}/api/todos`, { 19 | method: "POST", 20 | headers: { 21 | "Content-Type": "application/json", 22 | }, 23 | body: JSON.stringify(values), 24 | }).then((r) => r.json()); 25 | 26 | mutate(updated); 27 | form.reset(); 28 | setOpen(false); 29 | } 30 | 31 | return ( 32 | <> 33 | setOpen(false)} title="Create todo"> 34 |
35 | 42 |