나만의 레시피 기록 사이트 만들기(프론트+ 백엔드)

2024. 2. 3. 17:12React 정리

728x90

나만의 레시피 기록 사이트


Front End 작업내역순서


1. 최초 파일셋팅 (Page 폴더생성)


2. 테일윈드 css 도입

  •  테일윈드 패키지 설치
  •  설정 파일 추가
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

 

 tailwind.config.js에 다음 내용 추가

/** @type {import('tailwindcss').Config} */
export default {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

 

내 css파일에 다음 내용 추가 index.css

@tailwind base;
@tailwind components;
@tailwind utilities;

 

3. React router 도입

npm install react-router-dom

 

4. HTTP 요청을 위한 axios 도입

npm install axios

 

프론트 부분 코드

https://github.com/MWTeamB/Recipe_Frontend-

 

GitHub - MWTeamB/Recipe_Frontend-

Contribute to MWTeamB/Recipe_Frontend- development by creating an account on GitHub.

github.com

 

 

1. Mainpage.js

'나만의 레시피'라는 웹 사이트의 메인 페이지를 구성하는 코드입니다. 페이지 제목, 설명, 그리고 '서비스 이용하기'라는 링크가 포함되어 있습니다.


2. Refrigerator.js

모달 창을 열고 닫는 기능을 구현한 코드입니다. 'Allow'나 'Deny' 버튼을 누르면 각각 다른 동작을 하는 'handleConfirmation' 함수를 호출합니다.


3. Myrecipe.js

레시피를 불러오고, 검색하고, 모달 창을 열고 닫는 등의 기능을 수행하는 코드입니다. 레시피 데이터를 불러오는 'getList' 함수, 입력 값을 가져오는 'getValue' 함수, 모달 창을 열고 닫는 'openModal'과 'closeModal' 함수 등이 포함되어 있습니다. 또한, 레시피 목록 중에서 사용자의 입력 값이 포함된 레시피만 필터링하여 보여주는 기능도 있습니다.


각 코드에서 사용되는 'useState', 'useEffect' 등은 리액트의 상태 관리와 라이프사이클 관리를 위한 기능입니다. 'useState'는 컴포넌트의 내부 상태를 관리하고, 'useEffect'는 컴포넌트가 렌더링될 때, 업데이트될 때, 언마운트될 때 등의 시점에 특정 작업을 수행하도록 설정할 수 있습니다.

 

 

Backend 부분

 

https://github.com/MWTeamB/Recipe_Backend/blob/main/README.md

 

 

express 설치 명령어

npm i express



꼭 프로젝트의 최상위 폴더에서 실행해야 합니다.

express를 사용해야하는 프로젝트 마다 각각 설치해야 합니다.

node_modules 폴더에 설치 됩니다.



.gitignore의 역할


git 저장소에 쓸데없는 파일들이 저장되는 것을 막아준다.

git에 저장할 필요가 없는 파일들 : node_modules, package-lock.json



node_modules 폴더가 없을 때 복구하는 방법


npm install

이 명령어는 package.json 파일의 내용을 기초로 node_modules 폴더를 재 구성해줍니다.

.gitignore

# npm
node_modules
package-lock.json
*.log
*.gz

# Coveralls
.nyc_output
coverage

# Benchmarking
benchmarks/graphs

 

 express, DB 연동

PG 설치

  • npm install pg

PG sql

CREATE TABLE Recipes (
   recipe_id   SERIAL   PRIMARY KEY,
   Field      CHAR(100),
   description   CHAR(100),
   cooking_time   CHAR(100)
);

CREATE TABLE Ingredients (
   ingredient_id SERIAL PRIMARY KEY,
   name      CHAR(100)
);

CREATE TABLE Recipe_Ingredients (
   ingredient_id   int   REFERENCES Ingredients (ingredient_id),
   recipe_id   int   REFERENCES Recipes (recipe_id),
   PRIMARY KEY (ingredient_id, recipe_id)
);
ALTER TABLE Recipe_Ingredients
ADD CONSTRAINT fk_recipe_id
FOREIGN KEY (recipe_id)
REFERENCES Recipes(recipe_id)
ON DELETE CASCADE;

CREATE TABLE Storage (
  ingredient_id SERIAL PRIMARY KEY,
  name CHAR(100)
);

 

flyctl 설치 확인


flyctl 이 정상적으로 설치되었는지 확인하기 위하여, 운영체제의 터미널을 실행하여 아래의 명령어를 입력하여 동작하는지 확인합니다.

flyctl


만일 정상적으로 설치되었다면, fly 와 관련된 내용이 출력될 것입니다.

flyctl 을 이용하여 fly.io 에 로그인
flyctl 을 이용하여 fly 서비스와 연동하기 위해서는 계정을 연동하여야 합니다.

flyctl auth login


명령어를 입력하면 fly 서비스 페이지와 함께 로그인을 할 수 있게 됩니다.

flyctl 을 이용한 docker 어플리케이션 배포
이번 절에서는 flyctl 을 통해서 docker 어플리케이션을 빌드하고 배포하는 방법에 대해서 간략히 서술했습니다.

Dockerfile 이 있는 프로젝트의 경로에서 터미널을 실행합니다. 그 후, 하기의 명령어를 입력합니다.

fly launch

설정을 마친 후, 아래의 명령어를 입력하면 배포가 완료됩니다.

fly deploy

 

app.js

import cors from "cors";
import express from "express";
import pkg from "pg";

const { Pool } = pkg;
/*
Postgres cluster recipedb created
  Username:    postgres
  Password:    0lEslGEailOHOfq
  Hostname:    recipedb.internal
  Flycast:     fdaa:5:35ca:0:1::1b
  Proxy port:  5432
  Postgres port:  5433
  Connection string: postgres://postgres:0lEslGEailOHOfq@recipedb.flycast:5432
*/
const pool = new Pool({
  user: "postgres",
  password: "0lEslGEailOHOfq",
  host: "recipedb.internal",
  database: "postgres",
  port: 5432,
});

const app = express();

const corsOptions = {
  origin: "*",
};

app.use(cors(corsOptions));
app.use(express.json());

const port = 3000;

app.get("/", (req, res) => {
  res.send("Hello World!");
});

// 레시피 다건조회
app.get("/api/v1/recipes", async (req, res) => {
  try {
    const result = await pool.query("SELECT * FROM Recipes");
    const listrows = result.rows;

    res.json({
      resultCode: "S-1",
      msg: "성공",
      data: listrows,
    });
  } catch (error) {
    console.error(error);
    res.status(500).json({
      resultCode: "F-1",
      msg: "에러 발생",
      error: error.toString(),
    });
  }
});

// 레시피 단건조회
app.get("/api/v1/recipes/:id", async (req, res) => {
  try {
    const id = req.params.id;
    const result = await pool.query(
      "SELECT * FROM Recipes WHERE recipe_id = $1",
      [id]
    );
    const listrow = result.rows[0];

    res.json({
      resultCode: "S-1",
      msg: "성공",
      data: listrow,
    });
  } catch (error) {
    console.error(error);
    res.status(500).json({
      resultCode: "F-1",
      msg: "에러 발생",
      error: error.toString(),
    });
  }
});

// 레시피 생성
app.post("/api/v1/recipes", async (req, res) => {
  try {
    const { Field, description, cooking_time } = req.body;

    if (!Field || !description || !cooking_time) {
      res.status(400).json({
        resultCode: "F-1",
        msg: "Field, description, and cooking_time are required",
      });
      return;
    }

    const result = await pool.query(
      "INSERT INTO Recipes (Field, description, cooking_time) VALUES ($1, $2, $3) RETURNING *",
      [Field, description, cooking_time]
    );
    const recordrow = result.rows[0];

    res.json({
      resultCode: "S-1",
      msg: "성공",
      data: recordrow,
    });
  } catch (error) {
    console.error(error);
    res.status(500).json({
      resultCode: "F-1",
      msg: "에러 발생",
      error: error.toString(),
    });
  }
});

// 레시피 수정
app.patch("/api/v1/recipes/:id", async (req, res) => {
  const { id } = req.params;
  const { Field, description, cooking_time } = req.body;

  try {
    const checkResult = await pool.query(
      "SELECT * FROM Recipes WHERE recipe_id = $1",
      [id]
    );
    const listrow = checkResult.rows[0];

    if (listrow === undefined) {
      res.status(404).json({
        resultCode: "F-1",
        msg: "not found",
      });
      return;
    }

    await pool.query(
      "UPDATE Recipes SET Field = $1, description = $2, cooking_time = $3 WHERE recipe_id = $4",
      [Field, description, cooking_time, id]
    );

    const updatedResult = await pool.query(
      "SELECT * FROM Recipes WHERE recipe_id = $1",
      [id]
    );
    const updatedListrow = updatedResult.rows[0];
    res.json({
      resultCode: "S-1",
      msg: "성공",
      data: updatedListrow,
    });
  } catch (error) {
    console.error(error);
    res.status(500).json({
      resultCode: "F-1",
      msg: "에러 발생",
      error: error.toString(),
    });
  }
});

// 레시피 삭제
app.delete("/api/v1/recipes/:id", async (req, res) => {
  const { id } = req.params;

  const checkResult = await pool.query(
    "SELECT * FROM Recipes WHERE recipe_id = $1",
    [id]
  );
  const listrow = checkResult.rows[0];

  if (listrow === undefined) {
    res.status(404).json({
      resultCode: "F-1",
      msg: "not found",
    });
    return;
  }

  try {
    // Recipe_Ingredients 테이블에서 해당 레시피에 대한 모든 레코드 삭제
    await pool.query("DELETE FROM Recipe_Ingredients WHERE recipe_id = $1", [
      id,
    ]);

    // Recipes 테이블에서 해당 레시피 삭제
    await pool.query("DELETE FROM Recipes WHERE recipe_id = $1", [id]);

    res.json({
      resultCode: "S-1",
      msg: `${id}번 레시피가 삭제 되었습니다`,
    });
  } catch (error) {
    console.error(error);
    res.status(500).json({
      resultCode: "F-1",
      msg: "에러 발생",
      error: error.toString(),
    });
  }
});

// 레시피에 재료 추가
app.post("/api/v1/recipes/:id/ingredients", async (req, res) => {
  try {
    const { id } = req.params;
    const { ingredient_id } = req.body;

    if (!ingredient_id) {
      res.status(400).json({
        resultCode: "F-1",
        msg: "ingredient_id required",
      });
      return;
    }

    const result = await pool.query(
      "INSERT INTO Recipe_Ingredients (recipe_id, ingredient_id) VALUES ($1, $2) RETURNING *",
      [id, ingredient_id]
    );
    const recordrow = result.rows[0];

    res.json({
      resultCode: "S-1",
      msg: "성공",
      data: recordrow,
    });
  } catch (error) {
    console.error(error);
    res.status(500).json({
      resultCode: "F-1",
      msg: "에러 발생",
      error: error.toString(),
    });
  }
});

// 레시피의 재료 조회
app.get("/api/v1/recipes/:id/ingredients", async (req, res) => {
  try {
    const { id } = req.params;
    const result = await pool.query(
      "SELECT i.* FROM Ingredients i INNER JOIN Recipe_Ingredients ri ON i.ingredient_id = ri.ingredient_id WHERE ri.recipe_id = $1",
      [id]
    );
    const listrows = result.rows;

    res.json({
      resultCode: "S-1",
      msg: "성공",
      data: listrows,
    });
  } catch (error) {
    console.error(error);
    res.status(500).json({
      resultCode: "F-1",
      msg: "에러 발생",
      error: error.toString(),
    });
  }
});

// 재료 생성
app.post("/api/v1/ingredients", async (req, res) => {
  try {
    const { name } = req.body;

    if (!name) {
      res.status(400).json({
        resultCode: "F-1",
        msg: "name required",
      });
      return;
    }

    const result = await pool.query(
      "INSERT INTO Ingredients (name) VALUES ($1) RETURNING *",
      [name]
    );
    const recordrow = result.rows[0];

    res.json({
      resultCode: "S-1",
      msg: "성공",
      data: recordrow,
    });
  } catch (error) {
    console.error(error);
    res.status(500).json({
      resultCode: "F-1",
      msg: "에러 발생",
      error: error.toString(),
    });
  }
});

// 저장창고 기반 레시피 검색
app.get("/api/v1/recipes/search", async (req, res) => {
  try {
    const result = await pool.query(
      "SELECT DISTINCT R.* FROM Recipes R INNER JOIN Recipe_Ingredients RI ON R.recipe_id = RI.recipe_id WHERE RI.ingredient_id IN (SELECT ingredient_id FROM Storage)"
    );
    const listrows = result.rows;

    res.json({
      resultCode: "S-1",
      msg: "성공",
      data: listrows,
    });
  } catch (error) {
    console.error(error);
    res.status(500).json({
      resultCode: "F-1",
      msg: "에러 발생",
      error: error.toString(),
    });
  }
});

// 저장창고 재료 조회
app.get("/api/v1/storage", async (req, res) => {
  try {
    const result = await pool.query("SELECT * FROM Storage");
    const storageItems = result.rows;
    res.json({
      resultCode: "S-1",
      msg: "성공",
      data: storageItems,
    });
  } catch (error) {
    console.error(error);
    res.status(500).json({
      resultCode: "F-1",
      msg: "에러 발생",
      error: error.toString(),
    });
  }
});

// 저장창고 재료 추가
app.post("/api/v1/storage", async (req, res) => {
  const { name } = req.body;
  try {
    const result = await pool.query(
      "INSERT INTO Storage (name) VALUES ($1) RETURNING *",
      [name]
    );
    const newIngredient = result.rows[0];
    res.json({
      resultCode: "S-1",
      msg: "성공",
      data: newIngredient,
    });
  } catch (error) {
    console.error(error);
    res.status(500).json({
      resultCode: "F-1",
      msg: "에러 발생",
      error: error.toString(),
    });
  }
});

// 저장창고 재료 삭제
app.delete("/api/v1/storage/:id", async (req, res) => {
  const { id } = req.params;
  try {
    await pool.query("DELETE FROM Storage WHERE ingredient_id = $1", [id]);
    res.json({
      resultCode: "S-1",
      msg: `Ingredient with ID ${id} deleted successfully`,
    });
  } catch (error) {
    console.error(error);
    res.status(500).json({
      resultCode: "F-1",
      msg: "Error occurred",
      error: error.toString(),
    });
  }
});

// 저장창고 기반 레시피 검색
app.get("/api/v1/recipes/search", async (req, res) => {
  try {
    const result = await pool.query(
      "SELECT DISTINCT R.* FROM Recipes R INNER JOIN Recipe_Ingredients RI ON R.recipe_id = RI.recipe_id WHERE RI.ingredient_id IN (SELECT ingredient_id FROM Ingredients WHERE name IN (SELECT name FROM Storage))"
    );
    const listrows = result.rows;

    res.json({
      resultCode: "S-1",
      msg: "성공",
      data: listrows,
    });
  } catch (error) {
    console.error(error);
    res.status(500).json({
      resultCode: "F-1",
      msg: "에러 발생",
      error: error.toString(),
    });
  }
});

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`);
});

 

레시피 조회: 데이터베이스에서 모든 레시피를 조회하거나, 특정 ID의 레시피를 조회할 수 있습니다.


레시피 생성: 새로운 레시피를 생성할 수 있습니다. 레시피의 필드, 설명, 요리 시간을 제공해야 합니다.


레시피 수정: 기존의 레시피를 수정할 수 있습니다. 레시피의 필드, 설명, 요리 시간을 변경할 수 있습니다.


레시피 삭제: 특정 ID의 레시피를 삭제할 수 있습니다.


레시피에 재료 추가: 특정 레시피에 재료를 추가할 수 있습니다. 재료의 ID를 제공해야 합니다.


레시피의 재료 조회: 특정 레시피에 사용된 모든 재료를 조회할 수 있습니다.


재료 생성: 새로운 재료를 생성할 수 있습니다. 재료의 이름을 제공해야 합니다.


저장소 기반 레시피 검색: 저장소에 있는 재료를 기반으로 레시피를 검색할 수 있습니다.


저장소 재료 조회: 저장소에 있는 모든 재료를 조회할 수 있습니다.


저장소 재료 추가: 저장소에 재료를 추가할 수 있습니다. 재료의 이름을 제공해야 합니다.


저장소 재료 삭제: 저장소에서 특정 ID의 재료를 삭제할 수 있습니다.


이 코드는 사용자가 레시피를 관리하고, 재료를 관리하고, 저장소를 관리하는 등의 기능을 웹 브라우저를 통해 수행할 수 있게 해줍니다. 이러한 기능들은 레시피 관리 웹 애플리케이션에서 필요한 핵심 기능들입니다.