├── client
├── src
│ ├── index.css
│ ├── vite-env.d.ts
│ ├── chakra
│ │ └── theme.ts
│ ├── App.tsx
│ ├── main.tsx
│ └── components
│ │ ├── Navbar.tsx
│ │ ├── TodoForm.tsx
│ │ ├── TodoList.tsx
│ │ └── TodoItem.tsx
├── dist
│ ├── go.png
│ ├── react.png
│ ├── explode.png
│ ├── golang.png
│ ├── index.html
│ └── vite.svg
├── public
│ ├── go.png
│ ├── golang.png
│ ├── react.png
│ ├── explode.png
│ └── vite.svg
├── vite.config.ts
├── tsconfig.node.json
├── .gitignore
├── index.html
├── .eslintrc.cjs
├── tsconfig.json
├── package.json
└── README.md
├── .env.sample
├── .gitignore
├── air.toml
├── README.md
├── LICENSE
├── go.mod
├── main.go
├── COMPARISONS.md
└── go.sum
/client/src/index.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/.env.sample:
--------------------------------------------------------------------------------
1 | PORT=5000
2 | MONGODB_URI=
3 | ENV=development
--------------------------------------------------------------------------------
/client/dist/go.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/burakorkmez/react-go-tutorial/HEAD/client/dist/go.png
--------------------------------------------------------------------------------
/client/dist/react.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/burakorkmez/react-go-tutorial/HEAD/client/dist/react.png
--------------------------------------------------------------------------------
/client/public/go.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/burakorkmez/react-go-tutorial/HEAD/client/public/go.png
--------------------------------------------------------------------------------
/client/dist/explode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/burakorkmez/react-go-tutorial/HEAD/client/dist/explode.png
--------------------------------------------------------------------------------
/client/dist/golang.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/burakorkmez/react-go-tutorial/HEAD/client/dist/golang.png
--------------------------------------------------------------------------------
/client/public/golang.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/burakorkmez/react-go-tutorial/HEAD/client/public/golang.png
--------------------------------------------------------------------------------
/client/public/react.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/burakorkmez/react-go-tutorial/HEAD/client/public/react.png
--------------------------------------------------------------------------------
/client/public/explode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/burakorkmez/react-go-tutorial/HEAD/client/public/explode.png
--------------------------------------------------------------------------------
/client/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/client/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true,
8 | "strict": true
9 | },
10 | "include": ["vite.config.ts"]
11 | }
12 |
--------------------------------------------------------------------------------
/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 | GO + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/client/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | GO + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/client/src/chakra/theme.ts:
--------------------------------------------------------------------------------
1 | import { extendTheme, type ThemeConfig } from "@chakra-ui/react";
2 | import { mode } from "@chakra-ui/theme-tools";
3 |
4 | const config: ThemeConfig = {
5 | initialColorMode: "dark",
6 | useSystemColorMode: true,
7 | };
8 |
9 | // 3. extend the theme
10 | const theme = extendTheme({
11 | config,
12 | styles: {
13 | global: (props: any) => ({
14 | body: {
15 | backgroundColor: mode("gray.500", "")(props),
16 | },
17 | }),
18 | },
19 | });
20 |
21 | export default theme;
22 |
--------------------------------------------------------------------------------
/client/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:@typescript-eslint/recommended',
7 | 'plugin:react-hooks/recommended',
8 | ],
9 | ignorePatterns: ['dist', '.eslintrc.cjs'],
10 | parser: '@typescript-eslint/parser',
11 | plugins: ['react-refresh'],
12 | rules: {
13 | 'react-refresh/only-export-components': [
14 | 'warn',
15 | { allowConstantExport: true },
16 | ],
17 | },
18 | }
19 |
--------------------------------------------------------------------------------
/client/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { Container, Stack } from "@chakra-ui/react";
2 | import Navbar from "./components/Navbar";
3 | import TodoForm from "./components/TodoForm";
4 | import TodoList from "./components/TodoList";
5 |
6 | export const BASE_URL = import.meta.env.MODE === "development" ? "http://localhost:5000/api" : "/api";
7 |
8 | function App() {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
18 | }
19 |
20 | export default App;
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | .env
3 | node_modules
4 |
5 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
6 | *.o
7 | *.a
8 | *.so
9 |
10 | # Folders
11 | _obj
12 | _test
13 | vendor/
14 |
15 | # logs
16 | *.log
17 |
18 | # Compiled binary files
19 | *.exe
20 | *.out
21 |
22 | # macOS specific files
23 | .DS_Store
24 |
25 | # Go specific files
26 | # Compiled binary for packages and commands
27 | /bin/
28 | # Output of the go coverage tool, specifically when used with LiteIDE
29 | *.out
30 |
31 | # Dependency directories (remove the comment below if you are using dep)
32 |
--------------------------------------------------------------------------------
/air.toml:
--------------------------------------------------------------------------------
1 | root = "." # The root directory of the project
2 | tmp_dir = "tmp" # The temporary directory where air will store its temporary files
3 |
4 | [build] # The build configuration
5 | bin = "main" # The name of the binary file to be generated after building the project
6 | cmd = "go build -o {{.Output}} {{.Input}}" # The command to build the project
7 | exclude = ["tmp/*", "client/*"] # Specifies the directories to be excluded from monitoring for changes
8 | include = ["**/*.go"] # Specifies the file patterns to be included for monitoring.
9 | ignore = ["tmp/*"] # Specifies the files or directories to be ignored when triggering a build.
--------------------------------------------------------------------------------
/client/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App.tsx";
4 | import "./index.css";
5 | import { ChakraProvider } from "@chakra-ui/react";
6 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
7 | import theme from "./chakra/theme.ts";
8 |
9 | const queryClient = new QueryClient();
10 |
11 | ReactDOM.createRoot(document.getElementById("root")!).render(
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | );
20 |
--------------------------------------------------------------------------------
/client/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true
22 | },
23 | "include": ["src"],
24 | "references": [{ "path": "./tsconfig.node.json" }]
25 | }
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Let's Go! React with Go Complete Fullstack App - TypeScript, React Query, MongoDB, ChakraUI
2 |
3 | 
4 |
5 | [Video Tutorial on Youtube](https://youtu.be/zw8z_o_kDqc)
6 |
7 | Some Features:
8 |
9 | - ⚙️ Tech Stack: Go, React, TypeScript, MongoDB, TanStack Query, ChakraUI
10 | - ✅ Create, Read, Update, and Delete (CRUD) functionality for todos
11 | - 🌓 Light and Dark mode for user interface
12 | - 📱 Responsive design for various screen sizes
13 | - 🌐 Deployment
14 | - 🔄 Real-time data fetching, caching, and updates with TanStack Query
15 | - 🎨 Stylish UI components with ChakraUI
16 | - ⏳ And much more!
17 |
18 | ### .env file
19 |
20 | ```shell
21 | MONGO_URI=
22 | PORT=5000
23 | ENV=development
24 | ```
25 |
26 | ### Compile and run
27 |
28 | ```shell
29 | go run main.go
30 | ```
31 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@chakra-ui/react": "^2.8.2",
14 | "@emotion/react": "^11.11.4",
15 | "@emotion/styled": "^11.11.5",
16 | "@tanstack/react-query": "^5.32.1",
17 | "framer-motion": "^11.1.7",
18 | "react": "^18.2.0",
19 | "react-dom": "^18.2.0",
20 | "react-icons": "^5.2.0"
21 | },
22 | "devDependencies": {
23 | "@types/react": "^18.2.66",
24 | "@types/react-dom": "^18.2.22",
25 | "@typescript-eslint/eslint-plugin": "^7.2.0",
26 | "@typescript-eslint/parser": "^7.2.0",
27 | "@vitejs/plugin-react": "^4.2.1",
28 | "eslint": "^8.57.0",
29 | "eslint-plugin-react-hooks": "^4.6.0",
30 | "eslint-plugin-react-refresh": "^0.4.6",
31 | "typescript": "^5.2.2",
32 | "vite": "^5.2.0"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Burak
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/burakorkmez/react-go-tutorial
2 |
3 | go 1.19
4 |
5 | require (
6 | github.com/andybalholm/brotli v1.0.5 // indirect
7 | github.com/gofiber/fiber/v2 v2.52.4 // indirect
8 | github.com/golang/snappy v0.0.1 // indirect
9 | github.com/google/uuid v1.5.0 // indirect
10 | github.com/joho/godotenv v1.5.1 // indirect
11 | github.com/klauspost/compress v1.17.0 // indirect
12 | github.com/mattn/go-colorable v0.1.13 // indirect
13 | github.com/mattn/go-isatty v0.0.20 // indirect
14 | github.com/mattn/go-runewidth v0.0.15 // indirect
15 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
16 | github.com/rivo/uniseg v0.2.0 // indirect
17 | github.com/valyala/bytebufferpool v1.0.0 // indirect
18 | github.com/valyala/fasthttp v1.51.0 // indirect
19 | github.com/valyala/tcplisten v1.0.0 // indirect
20 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect
21 | github.com/xdg-go/scram v1.1.2 // indirect
22 | github.com/xdg-go/stringprep v1.0.4 // indirect
23 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
24 | go.mongodb.org/mongo-driver v1.15.0 // indirect
25 | golang.org/x/crypto v0.17.0 // indirect
26 | golang.org/x/sync v0.1.0 // indirect
27 | golang.org/x/sys v0.15.0 // indirect
28 | golang.org/x/text v0.14.0 // indirect
29 | )
30 |
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | # React + TypeScript + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
10 | ## Expanding the ESLint configuration
11 |
12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
13 |
14 | - Configure the top-level `parserOptions` property like this:
15 |
16 | ```js
17 | export default {
18 | // other rules...
19 | parserOptions: {
20 | ecmaVersion: 'latest',
21 | sourceType: 'module',
22 | project: ['./tsconfig.json', './tsconfig.node.json'],
23 | tsconfigRootDir: __dirname,
24 | },
25 | }
26 | ```
27 |
28 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
29 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
31 |
--------------------------------------------------------------------------------
/client/src/components/Navbar.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Flex, Button, useColorModeValue, useColorMode, Text, Container } from "@chakra-ui/react";
2 | import { IoMoon } from "react-icons/io5";
3 | import { LuSun } from "react-icons/lu";
4 |
5 | export default function Navbar() {
6 | const { colorMode, toggleColorMode } = useColorMode();
7 |
8 | return (
9 |
10 |
11 |
12 | {/* LEFT SIDE */}
13 |
19 |
20 | +
21 |
22 | =
23 |
24 |
25 |
26 | {/* RIGHT SIDE */}
27 |
28 |
29 | Daily Tasks
30 |
31 | {/* Toggle Color Mode */}
32 |
35 |
36 |
37 |
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/client/dist/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/src/components/TodoForm.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | import { Button, Flex, Input, Spinner } from "@chakra-ui/react";
3 | import { useMutation, useQueryClient } from "@tanstack/react-query";
4 | import { useState } from "react";
5 | import { IoMdAdd } from "react-icons/io";
6 | import { BASE_URL } from "../App";
7 |
8 | const TodoForm = () => {
9 | const [newTodo, setNewTodo] = useState("");
10 |
11 | const queryClient = useQueryClient();
12 |
13 | const { mutate: createTodo, isPending: isCreating } = useMutation({
14 | mutationKey: ["createTodo"],
15 | mutationFn: async (e: React.FormEvent) => {
16 | e.preventDefault();
17 | try {
18 | const res = await fetch(BASE_URL + `/todos`, {
19 | method: "POST",
20 | headers: {
21 | "Content-Type": "application/json",
22 | },
23 | body: JSON.stringify({ body: newTodo }),
24 | });
25 | const data = await res.json();
26 |
27 | if (!res.ok) {
28 | throw new Error(data.error || "Something went wrong");
29 | }
30 |
31 | setNewTodo("");
32 | return data;
33 | } catch (error: any) {
34 | throw new Error(error);
35 | }
36 | },
37 | onSuccess: () => {
38 | queryClient.invalidateQueries({ queryKey: ["todos"] });
39 | },
40 | onError: (error: any) => {
41 | alert(error.message);
42 | },
43 | });
44 |
45 | return (
46 |
65 | );
66 | };
67 | export default TodoForm;
68 |
69 | // STARTER CODE:
70 |
71 | // import { Button, Flex, Input, Spinner } from "@chakra-ui/react";
72 | // import { useState } from "react";
73 | // import { IoMdAdd } from "react-icons/io";
74 |
75 | // const TodoForm = () => {
76 | // const [newTodo, setNewTodo] = useState("");
77 | // const [isPending, setIsPending] = useState(false);
78 |
79 | // const createTodo = async (e: React.FormEvent) => {
80 | // e.preventDefault();
81 | // alert("Todo added!");
82 | // };
83 | // return (
84 | //
103 | // );
104 | // };
105 | // export default TodoForm;
106 |
--------------------------------------------------------------------------------
/client/src/components/TodoList.tsx:
--------------------------------------------------------------------------------
1 | import { Flex, Spinner, Stack, Text } from "@chakra-ui/react";
2 |
3 | import TodoItem from "./TodoItem";
4 | import { useQuery } from "@tanstack/react-query";
5 | import { BASE_URL } from "../App";
6 |
7 | export type Todo = {
8 | _id: number;
9 | body: string;
10 | completed: boolean;
11 | };
12 |
13 | const TodoList = () => {
14 | const { data: todos, isLoading } = useQuery({
15 | queryKey: ["todos"],
16 | queryFn: async () => {
17 | try {
18 | const res = await fetch(BASE_URL + "/todos");
19 | const data = await res.json();
20 |
21 | if (!res.ok) {
22 | throw new Error(data.error || "Something went wrong");
23 | }
24 | return data || [];
25 | } catch (error) {
26 | console.log(error);
27 | }
28 | },
29 | });
30 |
31 | return (
32 | <>
33 |
42 | Today's Tasks
43 |
44 | {isLoading && (
45 |
46 |
47 |
48 | )}
49 | {!isLoading && todos?.length === 0 && (
50 |
51 |
52 | All tasks completed! 🤞
53 |
54 |
55 |
56 | )}
57 |
58 | {todos?.map((todo) => (
59 |
60 | ))}
61 |
62 | >
63 | );
64 | };
65 | export default TodoList;
66 |
67 | // STARTER CODE:
68 |
69 | // import { Flex, Spinner, Stack, Text } from "@chakra-ui/react";
70 | // import { useState } from "react";
71 | // import TodoItem from "./TodoItem";
72 |
73 | // const TodoList = () => {
74 | // const [isLoading, setIsLoading] = useState(true);
75 | // const todos = [
76 | // {
77 | // _id: 1,
78 | // body: "Buy groceries",
79 | // completed: true,
80 | // },
81 | // {
82 | // _id: 2,
83 | // body: "Walk the dog",
84 | // completed: false,
85 | // },
86 | // {
87 | // _id: 3,
88 | // body: "Do laundry",
89 | // completed: false,
90 | // },
91 | // {
92 | // _id: 4,
93 | // body: "Cook dinner",
94 | // completed: true,
95 | // },
96 | // ];
97 | // return (
98 | // <>
99 | //
100 | // Today's Tasks
101 | //
102 | // {isLoading && (
103 | //
104 | //
105 | //
106 | // )}
107 | // {!isLoading && todos?.length === 0 && (
108 | //
109 | //
110 | // All tasks completed! 🤞
111 | //
112 | //
113 | //
114 | // )}
115 | //
116 | // {todos?.map((todo) => (
117 | //
118 | // ))}
119 | //
120 | // >
121 | // );
122 | // };
123 | // export default TodoList;
124 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "log"
7 | "os"
8 |
9 | "github.com/gofiber/fiber/v2"
10 | "github.com/joho/godotenv"
11 | "go.mongodb.org/mongo-driver/bson"
12 | "go.mongodb.org/mongo-driver/bson/primitive"
13 | "go.mongodb.org/mongo-driver/mongo"
14 | "go.mongodb.org/mongo-driver/mongo/options"
15 | )
16 |
17 | type Todo struct {
18 | ID primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty"`
19 | Completed bool `json:"completed"`
20 | Body string `json:"body"`
21 | }
22 |
23 | var collection *mongo.Collection
24 |
25 | func main() {
26 | fmt.Println("hello world")
27 |
28 | if os.Getenv("ENV") != "production" {
29 | // Load the .env file if not in production
30 | err := godotenv.Load(".env")
31 | if err != nil {
32 | log.Fatal("Error loading .env file:", err)
33 | }
34 | }
35 |
36 | MONGODB_URI := os.Getenv("MONGODB_URI")
37 | clientOptions := options.Client().ApplyURI(MONGODB_URI)
38 | client, err := mongo.Connect(context.Background(), clientOptions)
39 |
40 | if err != nil {
41 | log.Fatal(err)
42 | }
43 |
44 | defer client.Disconnect(context.Background())
45 |
46 | err = client.Ping(context.Background(), nil)
47 | if err != nil {
48 | log.Fatal(err)
49 | }
50 |
51 | fmt.Println("Connected to MONGODB ATLAS")
52 |
53 | collection = client.Database("golang_db").Collection("todos")
54 |
55 | app := fiber.New()
56 |
57 | // app.Use(cors.New(cors.Config{
58 | // AllowOrigins: "http://localhost:5173",
59 | // AllowHeaders: "Origin,Content-Type,Accept",
60 | // }))
61 |
62 | app.Get("/api/todos", getTodos)
63 | app.Post("/api/todos", createTodo)
64 | app.Patch("/api/todos/:id", updateTodo)
65 | app.Delete("/api/todos/:id", deleteTodo)
66 |
67 | port := os.Getenv("PORT")
68 | if port == "" {
69 | port = "5000"
70 | }
71 |
72 | if os.Getenv("ENV") == "production" {
73 | app.Static("/", "./client/dist")
74 | }
75 |
76 | log.Fatal(app.Listen("0.0.0.0:" + port))
77 |
78 | }
79 |
80 | func getTodos(c *fiber.Ctx) error {
81 | var todos []Todo
82 |
83 | cursor, err := collection.Find(context.Background(), bson.M{})
84 |
85 | if err != nil {
86 | return err
87 | }
88 |
89 | defer cursor.Close(context.Background())
90 |
91 | for cursor.Next(context.Background()) {
92 | var todo Todo
93 | if err := cursor.Decode(&todo); err != nil {
94 | return err
95 | }
96 | todos = append(todos, todo)
97 | }
98 |
99 | return c.JSON(todos)
100 | }
101 |
102 | func createTodo(c *fiber.Ctx) error {
103 | todo := new(Todo)
104 | // {id:0,completed:false,body:""}
105 |
106 | if err := c.BodyParser(todo); err != nil {
107 | return err
108 | }
109 |
110 | if todo.Body == "" {
111 | return c.Status(400).JSON(fiber.Map{"error": "Todo body cannot be empty"})
112 | }
113 |
114 | insertResult, err := collection.InsertOne(context.Background(), todo)
115 | if err != nil {
116 | return err
117 | }
118 |
119 | todo.ID = insertResult.InsertedID.(primitive.ObjectID)
120 |
121 | return c.Status(201).JSON(todo)
122 | }
123 |
124 | func updateTodo(c *fiber.Ctx) error {
125 | id := c.Params("id")
126 | objectID, err := primitive.ObjectIDFromHex(id)
127 |
128 | if err != nil {
129 | return c.Status(400).JSON(fiber.Map{"error": "Invalid todo ID"})
130 | }
131 |
132 | filter := bson.M{"_id": objectID}
133 | update := bson.M{"$set": bson.M{"completed": true}}
134 |
135 | _, err = collection.UpdateOne(context.Background(), filter, update)
136 | if err != nil {
137 | return err
138 | }
139 |
140 | return c.Status(200).JSON(fiber.Map{"success": true})
141 |
142 | }
143 |
144 | func deleteTodo(c *fiber.Ctx) error {
145 | id := c.Params("id")
146 | objectID, err := primitive.ObjectIDFromHex(id)
147 |
148 | if err != nil {
149 | return c.Status(400).JSON(fiber.Map{"error": "Invalid todo ID"})
150 | }
151 |
152 | filter := bson.M{"_id": objectID}
153 | _, err = collection.DeleteOne(context.Background(), filter)
154 |
155 | if err != nil {
156 | return err
157 | }
158 |
159 | return c.Status(200).JSON(fiber.Map{"success": true})
160 | }
161 |
--------------------------------------------------------------------------------
/client/src/components/TodoItem.tsx:
--------------------------------------------------------------------------------
1 | import { Badge, Box, Flex, Spinner, Text } from "@chakra-ui/react";
2 | import { FaCheckCircle } from "react-icons/fa";
3 | import { MdDelete } from "react-icons/md";
4 | import { Todo } from "./TodoList";
5 | import { useMutation, useQueryClient } from "@tanstack/react-query";
6 | import { BASE_URL } from "../App";
7 |
8 | const TodoItem = ({ todo }: { todo: Todo }) => {
9 | const queryClient = useQueryClient();
10 |
11 | const { mutate: updateTodo, isPending: isUpdating } = useMutation({
12 | mutationKey: ["updateTodo"],
13 | mutationFn: async () => {
14 | if (todo.completed) return alert("Todo is already completed");
15 | try {
16 | const res = await fetch(BASE_URL + `/todos/${todo._id}`, {
17 | method: "PATCH",
18 | });
19 | const data = await res.json();
20 | if (!res.ok) {
21 | throw new Error(data.error || "Something went wrong");
22 | }
23 | return data;
24 | } catch (error) {
25 | console.log(error);
26 | }
27 | },
28 | onSuccess: () => {
29 | queryClient.invalidateQueries({ queryKey: ["todos"] });
30 | },
31 | });
32 |
33 | const { mutate: deleteTodo, isPending: isDeleting } = useMutation({
34 | mutationKey: ["deleteTodo"],
35 | mutationFn: async () => {
36 | try {
37 | const res = await fetch(BASE_URL + `/todos/${todo._id}`, {
38 | method: "DELETE",
39 | });
40 | const data = await res.json();
41 | if (!res.ok) {
42 | throw new Error(data.error || "Something went wrong");
43 | }
44 | return data;
45 | } catch (error) {
46 | console.log(error);
47 | }
48 | },
49 | onSuccess: () => {
50 | queryClient.invalidateQueries({ queryKey: ["todos"] });
51 | },
52 | });
53 |
54 | return (
55 |
56 |
65 |
69 | {todo.body}
70 |
71 | {todo.completed && (
72 |
73 | Done
74 |
75 | )}
76 | {!todo.completed && (
77 |
78 | In Progress
79 |
80 | )}
81 |
82 |
83 | updateTodo()}>
84 | {!isUpdating && }
85 | {isUpdating && }
86 |
87 | deleteTodo()}>
88 | {!isDeleting && }
89 | {isDeleting && }
90 |
91 |
92 |
93 | );
94 | };
95 | export default TodoItem;
96 |
97 | // STARTER CODE:
98 |
99 | // import { Badge, Box, Flex, Text } from "@chakra-ui/react";
100 | // import { FaCheckCircle } from "react-icons/fa";
101 | // import { MdDelete } from "react-icons/md";
102 |
103 | // const TodoItem = ({ todo }: { todo: any }) => {
104 | // return (
105 | //
106 | //
115 | //
119 | // {todo.body}
120 | //
121 | // {todo.completed && (
122 | //
123 | // Done
124 | //
125 | // )}
126 | // {!todo.completed && (
127 | //
128 | // In Progress
129 | //
130 | // )}
131 | //
132 | //
133 | //
134 | //
135 | //
136 | //
137 | //
138 | //
139 | //
140 | //
141 | // );
142 | // };
143 | // export default TodoItem;
144 |
--------------------------------------------------------------------------------
/COMPARISONS.md:
--------------------------------------------------------------------------------
1 | # Equivalent of `npm init` in Go
2 |
3 | ```bash
4 | go mod init
5 | ```
6 |
7 | - `go mod init` is used to initialize a new module.
8 | - It creates a new `go.mod` file in the current directory.
9 | - The `go.mod` file contains information about the module, its dependencies, and the Go version.
10 |
11 | # Equivalent of `npm run start` in Go
12 |
13 | ```bash
14 | go run
15 | ```
16 |
17 | - `go run` is used to compile and run a Go program.
18 | - It compiles the program and executes it.
19 |
20 | # Equivalent of `npm install ` in Go
21 |
22 | ```bash
23 | go get
24 | ```
25 |
26 | - `go get` is not a package manager.
27 | - `go get` is used to download and install packages from remote repositories.
28 | - It does not handle versioning.
29 | - This command fetches the package and its dependencies (if any)
30 |
31 | # Equivalent of `package.json` in Go
32 |
33 | ```bash
34 | go.mod file
35 | ```
36 |
37 | - It contains information about the module, its dependencies, and the Go version.
38 |
39 | # Equivalent of `npm install` in Go
40 |
41 | ```bash
42 | go mod tidy
43 | ```
44 |
45 | - `go mod tidy` is used to add missing and remove unused modules.
46 | - It updates the go.mod file to use the latest version of the dependencies.
47 |
48 | # Equivalent of `JSON.stringify()` in Go
49 |
50 | ```go
51 | import "encoding/json"
52 |
53 | func main() {
54 | data := map[string]interface{}{
55 | "name": "John Doe",
56 | "age": 30,
57 | }
58 |
59 | jsonString, err := json.Marshal(data)
60 | if err != nil {
61 | fmt.Println(err)
62 | return
63 | }
64 |
65 | fmt.Println(string(jsonString))
66 | }
67 | ```
68 |
69 | - `json.Marshal()` is used to convert a Go data structure to a JSON string.
70 |
71 | # Equivalent of `JSON.parse()` in Go
72 |
73 | ```go
74 | import "encoding/json"
75 |
76 | func main() {
77 | jsonString := `{"name":"John Doe","age":30}`
78 |
79 | var data map[string]interface{}
80 | err := json.Unmarshal([]byte(jsonString), &data)
81 | if err != nil {
82 | fmt.Println(err)
83 | return
84 | }
85 |
86 | fmt.Println(data)
87 | }
88 | ```
89 |
90 | - `json.Unmarshal()` is used to convert a JSON string to a Go data structure.
91 |
92 | # Equivalent of `nodemon` in Go
93 |
94 | ```bash
95 | go install github.com/cosmtrek/air@latest
96 | ```
97 |
98 | - `air` is a live reload tool for Go applications.
99 | - It watches for file changes and automatically rebuilds and restarts the application.
100 | - It is similar to `nodemon` in the Node.js ecosystem.
101 | - There are other tools like `fresh` which can also be used for live reloading in Go.
102 |
103 | # Equivalent of `dotenv` in Go
104 |
105 | ```bash
106 | go get github.com/joho/godotenv
107 | ```
108 |
109 | - `godotenv` is a Go package that loads environment variables from a `.env` file.
110 | - It is similar to `dotenv` in the Node.js ecosystem.
111 | - It allows developers to store sensitive information like API keys, database URIs, etc., in a `.env` file and load them into the application.
112 |
113 | ## Code Example of Using `godotenv`
114 |
115 | ```go
116 | package main
117 |
118 | import (
119 | "fmt"
120 | "log"
121 | "os"
122 |
123 | "github.com/joho/godotenv"
124 | )
125 |
126 | func main() {
127 | err := godotenv.Load(".env")
128 | if err != nil {
129 | log.Fatal("Error loading .env file")
130 | }
131 |
132 | MONGODB_URI := os.Getenv("MONGODB_URI")
133 | }
134 | ```
135 |
136 | - In this example, we load environment variables from a `.env` file using `godotenv.Load(".env")`.
137 | - We can then access the environment variables using `os.Getenv("MONGODB_URI")`.
138 |
139 | # Equivalent of `Express.js` in Go
140 |
141 | ```bash
142 | go get github.com/gofiber/fiber/v2
143 | ```
144 |
145 | - `Fiber` is a web framework for Go that is inspired by Express.js.
146 | - It is fast, lightweight, and easy to use.
147 | - It provides a similar API to Express.js, making it easy for developers familiar with Express.js to transition to Go.
148 | - Other popular web frameworks in Go include `Gin` and `Echo`.
149 |
150 | # Equivalent of `Express.js Middleware` in Go
151 |
152 | ```go
153 | func main() {
154 | app := fiber.New()
155 |
156 | app.Use(middleware)
157 |
158 | app.Get("/", func(c *fiber.Ctx) error {
159 | return c.SendString("Hello, World!")
160 | })
161 |
162 | app.Listen(":3000")
163 | }
164 | func middleware(next http.Handler) http.Handler {
165 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
166 | // Middleware logic
167 |
168 | // .
169 | // .
170 | // .
171 |
172 | next.ServeHTTP(w, r)
173 | })
174 | }
175 | ```
176 |
177 | - In this example, we define a middleware function that takes the `next` handler as an argument.
178 | - The middleware function wraps the `next` handler and executes some logic before calling the `next` handler.
179 |
180 | # Equivalent of `Express.js Route Handling` in Go
181 |
182 | ```go
183 | func main() {
184 | app := fiber.New()
185 |
186 | app.Get("/", helloHandler)
187 |
188 | app.Listen(":3000")
189 | }
190 |
191 | func helloHandler(c *fiber.Ctx) error {
192 | return c.SendString("Hello, World!")
193 | }
194 | ```
195 |
196 | - In this example, we define a route handler function `helloHandler` that sends a response back to the client.
197 | - We then register the route handler with the `app.Get()` method, specifying the route path and the handler function.
198 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
2 | github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
3 | github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM=
4 | github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
5 | github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
6 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
7 | github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
8 | github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
9 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
10 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
11 | github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
12 | github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
13 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
14 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
15 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
16 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
17 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
18 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
19 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
20 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
21 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
22 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
23 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
24 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
25 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
26 | github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
27 | github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
28 | github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
29 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
30 | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
31 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
32 | github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
33 | github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
34 | github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
35 | github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
36 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
37 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
38 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
39 | go.mongodb.org/mongo-driver v1.15.0 h1:rJCKC8eEliewXjZGf0ddURtl7tTVy1TK3bfl0gkUSLc=
40 | go.mongodb.org/mongo-driver v1.15.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
41 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
42 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
43 | golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
44 | golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
45 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
46 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
47 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
48 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
49 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
50 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
51 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
52 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
53 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
54 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
55 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
56 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
57 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
58 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
59 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
60 | golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
61 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
62 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
63 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
64 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
65 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
66 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
67 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
68 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
69 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
70 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
71 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
72 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
73 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
74 |
--------------------------------------------------------------------------------