리액트와 node.js

sham·2021년 11월 1일
0

node.js를 통해 서버에 대해 공부를 시작하면서 항상 들었던 의문이 있었다.

node.js로 이런 걸 할 수 있는데, 그걸 프론트와 어떻게 연결시키지?

서버와 리액트 사이에 무언가가 단절되었다는 느낌을 항상 받았었는데, 이제서야 조금이나마 이해가 된 것 같아서 생각을 정리할 겸 글을 써본다.

node.js로 만든 서버

node.js는 자바스크립트를 돌릴 수 있는 환경이다. 자바스크립트 실행기이다. 서버도 돌릴 수 있다. node.js하면 서버가 떠오르는 것도 자바스크립트로 만든 서버를 돌리는 환경이기 때문이다.

const http = require('http');
const fs = require('fs').promises;

const users = {}; // 데이터 저장용

http.createServer(async (req, res) => {
  try {
    console.log(req.method, req.url);
    if (req.method === 'GET') {
      if (req.url === '/') {
        const data = await fs.readFile('./restFront.html');
        res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
        return res.end(data);
      } else if (req.url === '/about') {
        const data = await fs.readFile('./about.html');
        res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
        return res.end(data);
      } else if (req.url === '/users') {
        res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
        return res.end(JSON.stringify(users));
      }
      // /도 /about도 /users도 아니면
      try {
        const data = await fs.readFile(`.${req.url}`);
        return res.end(data);
      } catch (err) {
        // 주소에 해당하는 라우트를 못 찾았다는 404 Not Found error 발생
      }
    } else if (req.method === 'POST') {
      if (req.url === '/user') {
        let body = '';
        // 요청의 body를 stream 형식으로 받음
        req.on('data', (data) => {
          body += data;
        });
        // 요청의 body를 다 받은 후 실행됨
        return req.on('end', () => {
          console.log('POST 본문(Body):', body);
          const { name } = JSON.parse(body);
          const id = Date.now();
          users[id] = name;
          res.writeHead(201);
          res.end('등록 성공');
        });
      }
    } else if (req.method === 'PUT') {
      if (req.url.startsWith('/user/')) {
        const key = req.url.split('/')[2];
        let body = '';
        req.on('data', (data) => {
          body += data;
        });
        return req.on('end', () => {
          console.log('PUT 본문(Body):', body);
          users[key] = JSON.parse(body).name;
          return res.end(JSON.stringify(users));
        });
      }
    } else if (req.method === 'DELETE') {
      if (req.url.startsWith('/user/')) {
        const key = req.url.split('/')[2];
        delete users[key];
        return res.end(JSON.stringify(users));
      }
    }
    res.writeHead(404);
    return res.end('NOT FOUND');
  } catch (err) {
    console.error(err);
    res.writeHead(500);
    res.end(err);
  }
 })
  .listen(8082, () => {
    console.log('8082번 포트에서 서버 대기 중입니다');
  });

지금도 한참을 봐야 무슨 소리인지 이해가 가는 위의 코드는 순수 node.js로 짠 서버 코드다. 저 코드 한줄 한줄에 대한 내용은 아직도 완벽하게 이해했다 말할 수는 없지만, 이것 하나만큼은 자신 있게 말할 수 있다.

서버는 온갖 요청에 대한 온갖 응답의 모음이다!

어떤 URI로 들어왔는지, 어떤 요청을 했는지, 어떤 요청에 담긴 쿼리가 어떤 것인지에 따라 적절한 응답을 설정해주는 것이 서버의 역할이다. 응답으로 보내는 것은 그 무엇이든 될 수가 있지만, 기본적으로 json 형태로 보내는 것이 철칙이다.

리액트로 만든 프론트

리액트는 UI를 만드는 데 쓰이는 라이브러리이다. 싱글 페이지 어플리케이션, 가상 DOM, JSX을 이용한 동적인 페이지를 만드는데 특화되어 있다.

import React from 'react';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import { Main, Login, MyPiscine, RegisterPiscine, MyPiscineView } from 'pages';

const Routes = () => {
  return (
    <Router>
      <Switch>
        <Route exact path="/" component={Login} />
        <Route exact path="/main" component={Main} />
        <Route exact path="/myPiscine/:index" component={MyPiscine} />
        <Route exact path="/myPiscine/view/:index" component={MyPiscineView} />

        <Route
          exact
          path="/registerPiscine/:index"
          component={RegisterPiscine}
        />
      </Switch>
    </Router>
  );
};

export default Routes;

라우터 기능을 이용해서 URL에 맞는 페이지를 react에서 보여주는 것이 가능해진다. 서버에 URL에 맞는 html 페이지를 요청할 필요가 없어진 것이다.

둘이 합치면 무엇이 되나

리액트에서의 서버의 역할은 URL에 맞는 페이지를 쏘는 것이 아니다. 리액트에서 자체적으로 라우팅을 해서 URL에 맞는 페이지를 렌더링하기 때문.

리액트에서의 서버의 역할은 주로 REST API 를 구축하여 클라이언트의 요청을 할 때 마다 적절한 데이터를 전송하는데에 있다.

express에서의 설정만 해주면 json으로 자동 변환을 해준다.

const express = require("express");
const bodyParser = require("body-parser");
const app = express();
const port = process.env.PORT || 5000;

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.get("/", (req, res) => {
  res.send({ message: "main" });
});

app.get("/api/piscine", (req, res) => {
  try {
    res.send(["qwe", "zcxv", "asdg", "sa", "saf", "asffqw"]);
    console.log("success");
  } catch (e) {
    console.log(e);
  }
});
app.listen(port, () => {
  console.log(`${port} 포트에서 동작중!`);
});

서버 측 코드이다. localhost:5000/api/piscine 에 접근할 시 응답으로 다음과 같은 데이터를 전달해주도록 했다.

import { React, useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios';
import Header from 'components/Header';
import './Main.scss';

const Main = () => {
  const [myPiscine, setMyPiscine] = useState([]);
  const [piscineList, setPiscineList] = useState([]);

  const axiosAPI = async () => {
    try {
      const myPiscineData = await axios('/api/piscine');
      const piscineListData = await axios('/api/piscine');
      console.log(myPiscineData, piscineListData);
      setMyPiscine(myPiscineData.data);
      setPiscineList(piscineListData.data);
    } catch (e) {
      console.log(e);
    }
  };
  useEffect(() => {
    axiosAPI();
  }, []);

  return (
    <div className="main-container">
      <Header />
      {myPiscine && piscineList ? (
        <div className="main-page">
          <h1>참여 중인 과정</h1>

          <div className="parti">
            {myPiscine.map((e, index) => {
              const url = `/myPiscine/${index}`;
              return (
                <Link to={url}>
                  <div>{e}</div>
                </Link>
              );
            })}
          </div>
          <h1>등록 가능한 과정</h1>

          <div className="not-parti">
            {piscineList.map((e, index) => {
              const url = `/registerPiscine/${index}`;
              return (
                <Link to={url}>
                  <div>{e}</div>
                </Link>
              );
            })}
          </div>
        </div>
      ) : (
        <div>로딩 중!</div>
      )}
    </div>
  );
};

export default Main;

프론트쪽 코드이다. 서버에서 설정한 URL과 동일한 URL로 요청을 해서 응답을 받도록 했다.

데이터를 받기 전에 렌더링을 시도하면 오류가 날 것이기에 삼항연산자로 아직 데이터가 없을 시 임시 창을 띄우도록 했다.

axios로 응답을 받을 시 객체 형태로 오게 되는데, 응답헤더, 상태 코드 등도 포함되어 객체 내부에 있다.

  "proxy" : "http://localhost:5000/"

프론트의 package.json 파일에 다음과 같은 문장을 추가해준다.

"proxy" : "http://localhost:5000/" 으로 클라이언트가 우리가 만든 서버로 접속할 수 있게 해준다.

개발자 도구에 찍히는 주소는 기존의 프론트 포트인 3000번이지만 실제로는 proxy에 설정한 URL로 접근하게 된다.

profile
씨앗 개발자

0개의 댓글