# Presiapan project

Berikut adalah proyek sederhana yang terdiri dari frontend (React.js) dan backend (Go dengan Fiber) untuk aplikasi todo-list yang bisa melakukan operasi read, create, dan delete dengan PostgreSQL sebagai basis datanya.

#### Struktur Folder

```
todo-frontend/
├── public
│   └── index.html
├── src
│   ├── App.js
│   ├── index.js
│   ├── components
│   │   └── ProductList.js
├── package.json
└── .env

todo-backend/
├── main.go
├── go.mod
├── go.sum
└── database
    └── connection.go
```

#### todo-frontend

**1. File: `todo-frontend/public/index.html`**

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Todo List</title>
</head>
<body>
  <div id="root"></div>
</body>
</html>
```

**2. File: `todo-frontend/src/index.js`**

```javascript
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
```

**3. File: `todo-frontend/src/App.js`**

```javascript
import React from 'react';
import ProductList from './components/ProductList';

function App() {
  return (
    <div className="App">
      <ProductList />
    </div>
  );
}

export default App;
```

**4. File: `todo-frontend/src/components/ProductList.js`**

```javascript
import React, { useState, useEffect } from 'react';
import axios from 'axios';

const ProductList = () => {
  const [products, setProducts] = useState([]);
  const [name, setName] = useState('');
  const [price, setPrice] = useState('');
  const [stock, setStock] = useState('');

  useEffect(() => {
    fetchProducts();
  }, []);

  const fetchProducts = async () => {
    const response = await axios.get(`${process.env.REACT_APP_API_URL}/products`);
    setProducts(response.data);
  };

  const createProduct = async () => {
    const newProduct = { name, price: parseFloat(price), stock: parseInt(stock) };
    await axios.post(`${process.env.REACT_APP_API_URL}/products`, newProduct);
    fetchProducts();
    setName('');
    setPrice('');
    setStock('');
  };

  const deleteProduct = async (id) => {
    await axios.delete(`${process.env.REACT_APP_API_URL}/products/${id}`);
    fetchProducts();
  };

  return (
    <div>
      <h1>Product List</h1>
      <ul>
        {products.map(product => (
          <li key={product.id}>
            {product.name} - ${product.price} - Stock: {product.stock}
            <button onClick={() => deleteProduct(product.id)}>Delete</button>
          </li>
        ))}
      </ul>
      <div>
        <h2>Create Product</h2>
        <input
          type="text"
          placeholder="Name"
          value={name}
          onChange={e => setName(e.target.value)}
        />
        <input
          type="text"
          placeholder="Price"
          value={price}
          onChange={e => setPrice(e.target.value)}
        />
        <input
          type="text"
          placeholder="Stock"
          value={stock}
          onChange={e => setStock(e.target.value)}
        />
        <button onClick={createProduct}>Create</button>
      </div>
    </div>
  );
};

export default ProductList;
```

**5. File: `todo-frontend/package.json`**

```json
{
  "name": "todo-frontend",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.17.0",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "axios": "^1.7.2",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}
```

**6. File: `todo-frontend/.env`**

```env
REACT_APP_API_URL=http://localhost:3000
```

#### todo-backend

**1. File: `todo-backend/main.go`**

```go
package main

import (
	"todo-backend/database"
	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/cors"
	"gorm.io/gorm"
)

type Product struct {
	ID    uint    `json:"id" gorm:"primaryKey"`
	Name  string  `json:"name"`
	Price float64 `json:"price"`
	Stock int     `json:"stock"`
}

var DB *gorm.DB

func main() {
	database.Connect()
	DB = database.DB

	DB.AutoMigrate(&Product{})

	app := fiber.New()

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

	app.Get("/products", GetProducts)
	app.Post("/products", CreateProduct)
	app.Delete("/products/:id", DeleteProduct)

	app.Listen(":3000")
}

func GetProducts(c *fiber.Ctx) error {
	var products []Product
	DB.Find(&products)
	return c.JSON(products)
}

func CreateProduct(c *fiber.Ctx) error {
	product := new(Product)
	if err := c.BodyParser(product); err != nil {
		return c.Status(400).SendString(err.Error())
	}
	DB.Create(&product)
	return c.JSON(product)
}

func DeleteProduct(c *fiber.Ctx) error {
	id := c.Params("id")
	var product Product
	DB.First(&product, id)
	if product.ID == 0 {
		return c.Status(404).SendString("No Product Found with ID")
	}
	DB.Delete(&product)
	return c.SendString("Product Successfully deleted")
}
```

**2. File: `todo-backend/database/connection.go`**

```go
package database

import (
	"fmt"
	"log"
	"os"
	"time"

	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

var DB *gorm.DB

func Connect() {
	var err error
	dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable",
		os.Getenv("DB_HOST"),
		os.Getenv("DB_USER"),
		os.Getenv("DB_PASSWORD"),
		os.Getenv("DB_DATABASE"),
		os.Getenv("DB_PORT"),
	)

	DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Info),
	})
	if err != nil {
		log.Fatal("Failed to connect to database: ", err)
	}

	db, err := DB.DB()
	if err != nil {
		log.Fatal("Failed to get database connection: ", err)
	}

	db.SetMaxIdleConns(10)
	db.SetMaxOpenConns(100)
	db.SetConnMaxLifetime(time.Hour)
}
```

**3. File: `todo-backend/go.mod`**

```go
module todo-backend

go 1.18

require (
	github.com/gofiber/fiber/v2 v2.52.4
	gorm.io/driver/postgres v1.5.7
	gorm.io/gorm v1.25.10
)
```

**4. File: `todo-backend/go.sum`**

(Secara otomatis dibuat oleh Go saat Anda menjalankan `go mod tidy` atau menambahkan dependensi.)

#### Menjalankan Proyek

**Backend**

1. **Pastikan PostgreSQL Berjalan**:
   * Pastikan PostgreSQL berjalan dan dapat diakses dengan kredensial yang diberikan.
   * Anda bisa menggunakan Docker untuk menjalankan PostgreSQL:

     ```sh
     docker run --name postgres-db -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=root -e POSTGRES_DB=marketplace -p 5432:5432 -d postgres
     ```
2. **Set Environment Variables**:
   * Buat file `.env` di root folder `todo-backend` dan isi dengan:

     ```env
     DB_HOST=localhost
     DB_PORT=5432
     DB_USER=postgres
     DB_PASSWORD=root
     DB_DATABASE=marketplace
     ```
3. **Jalankan Backend**:
   * Di folder \`todo-back

end\`, jalankan: `sh go mod tidy go run main.go`

**Frontend**

1. **Install Dependencies**:
   * Di folder `todo-frontend`, jalankan:

     ```sh
     npm install
     ```
2. **Jalankan Frontend**:
   * Masih di folder `todo-frontend`, jalankan:

     ```sh
     npm start
     ```

Frontend akan berjalan di `http://localhost:3000` dan akan berkomunikasi dengan backend yang juga berjalan di port 3000.

Dengan mengikuti langkah-langkah di atas, Anda akan memiliki aplikasi todo-list sederhana yang dapat melakukan operasi read, create, dan delete menggunakan React untuk frontend dan Go dengan Fiber untuk backend.

{% embed url="<https://github.com/zakimaliki/fullstack-docker>" %}
