리액트와 node.js 를 통해 사진첩 만들기

2024. 1. 15. 23:39React 정리

728x90
반응형

React와 Express를 활용한 사진 앨범 만들기
안녕하세요. 오늘은 React와 Express를 활용하여 간단한 사진 앨범 웹 어플리케이션을 만드는 방법에 대해 자세히 알아보겠습니다.

1. 프로젝트 구조
프로젝트는 크게 두 부분으로 나뉘어져 있습니다. 프론트엔드는 React를 사용하며, 백엔드는 Express.js와 MySQL을 사용합니다.

프론트엔드는 다음과 같은 구조로 이루어져 있습니다:

App.js: 라우팅을 담당하는 메인 컴포넌트입니다.
pages: 각 페이지 컴포넌트를 담고 있습니다. (Home, MyPage, UploadPage 등)
components: 재사용 가능한 컴포넌트들을 담고 있습니다.
백엔드는 Express.js를 사용하여 API를 구축하며, 데이터는 MySQL 데이터베이스에 저장됩니다.



2. 코드 분석
2.1 App.js
이 파일은 React 앱의 진입점입니다. 이곳에서 react-router-dom을 사용하여 라우팅을 설정합니다. 각 경로에 대한 페이지를 매핑하여, 사용자가 특정 URL을 방문했을 때 어떤 컴포넌트를 보여줄지 결정합니다.

import { BrowserRouter, Route, Routes } from "react-router-dom";
import Header from "./components/Header";
import Home from "./pages/Home";
import MyPage from "./pages/MyPage";
import MapPoto from "./pages/MapPoto"; // MapPoto import
import UploadPage from "./pages/UploadPage"; // UploadPage import

function App() {
  return (
    <BrowserRouter>
      <Header />
      <Routes>
        <Route index element={<Home />} />
        <Route path="mypage" element={<MyPage />} />
        <Route path="upload" element={<UploadPage />} />
        
        <Route path="MapPoto" element={<MapPoto />} /> 
      </Routes>
    </BrowserRouter>
  );
}

export default App;

 

2.2 Home.js

이 페이지는 사용자에게 사진을 업로드할 수 있는 버튼을 제공합니다. useNavigate 훅을 사용하여 버튼 클릭 시 MapPoto 페이지로 이동하는 함수를 만듭니다.

import React from "react";
import { useNavigate } from "react-router-dom";
import styled, { keyframes } from "styled-components";
import mapImage from "../components/images/mapimg.png";

const imageAppear = keyframes`
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
`;

const textAppear = keyframes`
  0% {
    transform: scale(0.8);
    opacity: 0;
  }
  100% {
    transform: scale(1);
    opacity: 1;
  }
`;

const buttonGlow = keyframes`
  0% {
    box-shadow: 0 0 5px #333;
  }
  50% {
    box-shadow: 0 0 20px #333, 0 0 30px #333;
  }
  100% {
    box-shadow: 0 0 5px #333;
  }
`;

const Home = () => {
  const navigate = useNavigate();

  const handleButtonClick = () => {
    navigate("/MapPoto");
  };

  return (
    <HeroContainer>
      <HeroContent>
        <HeroButton onClick={handleButtonClick}>사진 저장함</HeroButton>
      </HeroContent>
    </HeroContainer>
  );
};

const HeroContainer = styled.section`
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  background-image: url(${mapImage});
  background-size: 50%;
  background-position: center;
  background-repeat: no-repeat;
  animation: ${imageAppear} 3s ease-in-out forwards; // 배경 이미지에 효과를 추가합니다.
`;

const HeroContent = styled.div`
  text-align: center;
  animation: ${textAppear} 5s ease-in-out forwards;
`;

const HeroButton = styled.button`
  padding: 0.8rem 2rem;
  font-size: 1.1rem;
  color: white;
  background-color: #333;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.3s ease;
  animation: ${buttonGlow} 2s infinite;

  &:hover {
    background-color: #777;
  }
`;

export default Home;

 

2.3 MyPage.js
이 페이지는 사용자가 업로드한 사진들을 보여줍니다. useState와 useEffect 훅을 사용하여 백엔드 서버로부터 사진 데이터를 받아와 상태로 관리합니다.

import React, { useState, useEffect } from "react";
import axios from "axios";
import styled from "styled-components";

const Image = styled.img`
  margin: 10px;
  width: 200px;
  height: 200px;
`;

function MyPage() {
  const [photos, setPhotos] = useState([]);

  useEffect(() => {
    axios
      .get("http://localhost:5000/photos")
      .then((response) => {
        setPhotos(response.data);
      })
      .catch((error) => {
        console.log(error);
      });
  }, []);

  return (
    <div>
      {photos.map((photo) => (
        <Image
          key={photo.id}
          src={photo.url} // URL이 완전한 형태로 저장되어 있다고 가정
          alt="사진"
        />
      ))}
    </div>
  );
}

export default MyPage;

 

2.4 Express 서버
Express 서버는 API를 제공하며, 사진을 업로드하고 저장하는 데 사용됩니다. multer 미들웨어를 사용하여 파일 업로드를 처리하며, cors 미들웨어를 사용하여 CORS 문제를 해결합니다.

import express from "express";
import multer from "multer";
import cors from "cors";
import path from "path";
import { fileURLToPath } from "url";
import { dirname } from "path";
import mysql from "mysql2/promise";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const app = express();
const port = 5000;

app.use(cors());

app.use("/uploads", express.static(path.join(__dirname, "uploads")));

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

const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, "uploads/");
  },
  filename: (req, file, cb) => {
    cb(null, Date.now() + path.extname(file.originalname)); // appending extension
  },
});

const upload = multer({ storage: storage });

const pool = mysql.createPool({
  host: "localhost",
  user: "root",
  password: "",
  database: "photo_album",
  waitForConnections: true,
  connectionLimit: 10,
  queueLimit: 0,
});

app.post("/upload", upload.array("photo"), async (req, res) => {
  if (!req.files) {
    return res.status(400).send("No files were uploaded.");
  }

  try {
    const insertPromises = req.files.map((file) => {
      const url = `./uploads/${file.filename}`;
      return pool.query(
        "INSERT INTO photos (upload_date, file_name, file_path, url) VALUES (NOW(), ?, ?, ?)",
        [file.originalname, `/uploads/${file.originalname}`, url]
      );
    });

    await Promise.all(insertPromises);

    res.send(req.files.map((file) => ({ url: `/uploads/${file.filename}` })));
  } catch (err) {
    console.error(err);
    res.status(500).send("Server Error");
  }
});

app.get("/photos", async (req, res) => {
  try {
    const [rows] = await pool.query("SELECT * FROM photos");
    res.send(rows);
  } catch (err) {
    console.error(err);
    res.status(500).send("Server Error");
  }
});

app.listen(port, () => {
  console.log(`Server is listening at http://localhost:${port}`);
});

 

2.5 Vite 설정
Vite는 프론트엔드 개발 환경을 구성하는 데 사용됩니다. 여기서는 프록시 설정을 통해 백엔드 API에 접근하도록 설정되어 있습니다.

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      "/api": "http://localhost:5000",
    },
  },
});

 

db.sql

# DB 생성
DROP DATABASE IF EXISTS photo_album;
CREATE DATABASE photo_album;
USE photo_album;

# 테이블 생성
CREATE TABLE photos (
    id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
    upload_date DATETIME NOT NULL,
    file_name VARCHAR(255) NOT NULL,
    file_path VARCHAR(255) NOT NULL,
    url VARCHAR(255) NOT NULL  -- 'url' 컬럼 추가
);

# 데이터 생성 예시
INSERT INTO photos
SET upload_date = NOW(),
file_name = 'image01.jpg',
file_path = '/uploads/image01.jpg',
url = 'http://localhost:5000/uploads/image01.jpg';  -- 'url' 값 추가

INSERT INTO photos
SET upload_date = NOW(),
file_name = 'image02.jpg',
file_path = '/uploads/image02.jpg',
url = 'http://localhost:5000/uploads/image02.jpg';  -- 'url' 값 추가

INSERT INTO photos
SET upload_date = NOW(),
file_name = 'image03.jpg',
file_path = '/uploads/image03.jpg',
url = 'http://localhost:5000/uploads/image03.jpg';  -- 'url' 값 추가