서버 만들기

odada·2024년 12월 9일
0

node.js

목록 보기
5/11

node.js로 서버 만들기 - node.js 배우기

1. 서버란?

서버와 클라이언트

서버는 요청을 받는 응답자이고, 클라이언트는 요청을 보내는 요청자입니다.

  • 클라이언트: 웹 브라우저(Chrome, Safari).
  • 서버: 네이버, 구글 같은 웹사이트를 운영하는 컴퓨터.

쉽게 서버의 동작 이해하기

  • 브라우저에서 "구글 검색"을 입력(요청)하면,
  • 구글의 서버가 요청을 받아서 검색 결과를 준비(처리)한 뒤,
  • 준비된 검색 결과를 브라우저로 보냄(응답).

서버의 예

  1. 웹 서버: 웹사이트를 보여주는 서버.

    • 예: 네이버, 구글, 유튜브.
  2. 파일 서버: 파일을 저장하고 전송하는 서버.

    • 예: Google Drive, Dropbox.
  3. 게임 서버: 온라인 게임에서 플레이어 간 연결을 관리하는 서버.

    • 예: LOL, PUBG.
  4. 호스팅 서버: 웹사이트를 저장하고 관리하는 서버.

    • 예: AWS, Azure.

2. 프로젝트 시작

- 프로젝트 생성

  • NPM 명령어 로 프로젝트를 생성합니다.

  • node-server 프로젝트 폴더를 생성하고, npm init 명령어로 package.json 파일을 생성합니다.

$ mkdir node-server
$ cd node-server
$ npm init -y

2. 서버 만들기

서버 만들기 전에 w3schools의 Node.js HTTP 모듈을 참고하세요.

- 간단한 HTTP 서버 만들기

HTTP 서버는 클라이언트(브라우저)의 요청을 받고, 이에 응답하는 프로그램입니다.
Node.js는 http 모듈을 사용해 쉽게 서버를 생성할 수 있습니다.

  • http.createServer() 메서드로 서버를 생성합니다.
  • server.listen() 메서드로 서버를 실행합니다.
// server.js
// Node.js의 기본 내장 모듈인 'http' 모듈을 불러옵니다.
const http = require('http');

// http 서버를 생성하는 메서드
// 콜백 함수로 request(요청)과 response(응답) 객체를 매개변수로 받습니다.
const server = http.createServer((req, res) => {
    // 요청이 들어오면 응답합니다.
    res.writeHead(200, { 'Content-Type': 'text/html' }); // 응답 헤더 설정
    res.write('<h1>Hello, Node.js!</h1>'); // 응답 본문
    res.end('<p>http 모듈 공부 중...</p>'); // 응답 종료
});

// 서버가 8080 포트에서 실행되도록 설정합니다.
// .listen() : 특정 포트에서 서버를 실행하고 클라이언트의 요청을 기다립니다.
server.listen(8080, () => {
    console.log('8080 포트에서 서버가 실행 중입니다.');
});
  • localhost는 자신의 컴퓨터를 가리키는 주소입니다.
  • 8080은 서버가 실행될 포트 번호입니다.
$ node server.js
8080 포트에서 서버가 실행 중입니다.

- http 상태 코드

HTTP 상태 코드는 클라이언트의 요청에 대한 서버의 응답 상태를 나타내는 코드입니다.

  • 1xx: 처리중(Informational) / 요청을 받았으며 프로세스를 계속합니다.
  • 2xx: 성공(Success) / 요청을 성공적으로 받았으며 이해했고 수용했습니다.
  • 3xx: 리다이렉션(Redirection) / 요청을 완료하기 위해 추가 동작이 필요합니다.
  • 4xx: 클라이언트 오류(Client Error) / 요청의 문법이 잘못되었거나 요청을 처리할 수 없습니다.
  • 5xx: 서버 오류(Server Error) / 서버가 유효한 요청에 대해 충족을 실패했습니다.

참고: MDN HTTP 상태 코드

주요 상태 코드

  • 200: OK / 요청이 성공했습니다.
  • 404: Not Found / 요청한 페이지를 찾을 수 없습니다.
  • 500: Internal Server Error / 서버에 오류가 발생했습니다.

- 리스닝 이벤트(이벤트 리스너)

Node.js에서 리스닝 이벤트는 특정 상황(이벤트)이 발생했을 때 실행되는 코드
서버가 클라이언트의 요청을 받아들이기 시작할 때 발생하는 이벤트입니다.

  • server.listen() 메서드는 서버를 실행하고 클라이언트의 요청을 기다리는 메서드입니다.
  • 서버가 정상적으로 실행되면 listening 이벤트가 발생합니다.
  • server.on('listening', 콜백함수)로 리스닝 이벤트를 등록할 수 있습니다.
  • server.on('error', 콜백함수)로 에러 이벤트를 등록할 수 있습니다.
// server.js

const http = require('http');

// 서버 생성
const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/html' });
    res.write('<h1>Hello, Node.js!</h1>');
    res.end('<p>http 모듈 공부 중...</p>');
});

// `listening` 이벤트 등록
server.on('listening', () => {
  console.log('서버가 정상적으로 실행되었습니다!');
});

// `error` 이벤트 등록
server.on('error', (error) => {
  console.error('서버 실행 중 오류 발생:', error.message);
});

// 서버 실행
server.listen(8080, () => {
  console.log('8080 포트에서 서버가 실행 중입니다.');
});
$ node server.js
8080 포트에서 서버가 실행 중입니다.
서버가 정상적으로 실행되었습니다!

크롬의 주소창에 http://localhost:8080을 입력하면, "Hello, Node.js!"가 출력됩니다.

참고

터미널에서는 메시지가 보이지만, 크롬의 개발자 도구 콘솔창에서 메시지가 보이지 않는 이유는 터미널 출력(console.log)와 클라이언트(브라우저) 출력이 서로 다른 개념이기 때문입니다.

이유
1. 서버 측 로그

- console.log는 Node.js 서버에서 실행되는 코드에서 발생한 로그를 출력하기 때문에
- 위 메시지는 서버의 터미널에서만 확인할 수 있습니다.
  1. 클라이언트(브라우저) 로그

    • 브라우저의 개발자 도구 콘솔창은 서버에서 받은 HTML, JavaScript 등의 응답에서 발생한 로그를 보여주는 곳입니다.
    • 현재 서버가 브라우저로 응답한 HTML에는 클라이언트 측 로그를 출력하는 코드가 없기 때문에 크롬 콘솔창에서 메시지가 보이지 않습니다.

참고

파일을 수정할 때마다 서버를 재시작하는 것이 번거롭다면, nodemon 패키지를 사용해보세요.

$ npm install -g nodemon
$ nodemon server.js
{
  "scripts": {
    "start": "nodemon server.js"
  }
}
$ npm start

- 파일을 보내는 응답 코드

응답 콜백에 html을 직접 넣어주는 것이 아니라 파일을 따로 만들어 파일시스템(fs)을 이용해 읽어서 보내는 방법

  • fs 파일을 읽어오는 모듈
  • fs.promises.readFile() 파일을 읽어오는 메서드
  • res.end(data)로 파일을 응답 본문으로 보내면서 요청 종료
$ touch index.html
<!-- index.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>Node.js로 서버 만들기</title>
</head>
<body>
    <h1>Node.js로 서버 만들기</h1>
    <p>파일을 읽어와 응답하는 서버</p>
</body>
</html>
  • aync/await 문법을 사용하면 비동기 처리를 동기 처리처럼 작성할 수 있습니다.
  • try-catch 문법을 사용하면 에러 처리를 쉽게 할 수 있습니다.
// fs-test.js
const http = require('http');
const fs = require('fs');

const server = http.createServer(async (req, res) => {
    try {
        // fs.promises.readFile() : 파일을 읽어오는 메서드
        const data = await fs.promises.readFile('./index.html');
        // 200이면 성공
        res.writeHead(200, { 'Content-Type': 'text/html' });
        // 파일을 읽어온 data를 응답 본문으로 보내면서 요청 종료
        res.end(data);
    } catch (error) {
        console.error(error);
        // 500이면 서버 오류
        res.writeHead(500, { 'Content-Type': 'text/plain' });
        // 에러 메시지를 응답 본문으로 보내면서 요청 종료
        res.end(error.message);
    }
});

// 8080 서버 실행
server.listen(8080, () => {
    console.log('8080 포트에서 서버가 실행 중입니다.');
});

3. 요청 객체(req), 응답 객체(res)

Node.js의 HTTP 모듈을 사용하면 서버에서 요청 객체(req)와 응답 객체(res)를 사용할 수 있습니다.

- 요청 객체(req)

  • req.url : 클라이언트가 요청한 URL 주소
  • req.method : 클라이언트가 요청한 HTTP 메서드
  • req.headers : 클라이언트의 요청 헤더 정보

- 응답 객체(res)

  • res.writeHead(상태코드, 헤더객체) : 응답 헤더 설정
  • res.write(데이터) : 응답 본문 작성
  • res.end(데이터) : 응답 종료

- REST를 통한 페이지 라우팅

REST(Representational State Transfer)는 자원을 이름(자원의 표현)으로 구분하여 해당 자원의 상태(정보)를 주고 받는 모델입니다.

  • req.url로 요청한 URL 주소를 확인하고, 해당 URL에 따라 다른 응답을 보내는 방식
  • if문을 사용해 URL에 따라 다른 응답을 보내는 방식을 라우팅이라고 합니다.
  • 이렇게 URL에 따라 다른 응답을 보내는 것을 RESTful API라고 합니다.
<!-- index.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>Node.js로 서버 만들기</title>
</head>
<body>
    <h1>Main</h1>
</body>
</html>
<!-- about.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>About : Node.js로 서버 만들기</title>
</head>
<body>
    <h1>About</h1>
</body>
</html>
// rest.js
const http = require('http');
const fs = require('fs');

const server = http.createServer(async (req, res) => {
    try {
        if (req.url === '/') {
            const data = await fs.promises.readFile('./index.html');
            res.writeHead(200, { 'Content-Type': 'text/html' });
            res.end(data);
        } else if (req.url === '/about') {
            const data = await fs.promises.readFile('./about.html');
            res.writeHead(200, { 'Content-Type': 'text/html' });
            res.end(data);
        } else {
            res.writeHead(404, { 'Content-Type': 'text/html' });
            res.end('<h1>404 Not Found</h1>');
        }
    } catch (error) {
        console.error(error);
        res.writeHead(500, { 'Content-Type': 'text/plain' });
        res.end(error.message);
    }
});

server.listen(8080, () => {
    console.log('8080 포트에서 서버가 실행 중입니다.');
});

서버를 실행하고, 크롬의 주소창에 http://localhost:8080, http://localhost:8080/about를 입력해보세요.

크롬 브라우저에서 개발자 도구를 열고 Network 탭을 확인하면, 요청과 응답 상태 코드를 확인할 수 있습니다.

4. 쿼리스트링(Query String)

쿼리스트링(Query String)은 URL 주소에 데이터를 포함하여 서버로 전달하는 방식입니다.

  • req.url로 요청한 URL 주소를 확인하고, URL에 포함된 쿼리스트링을 분석하는 방식
  • URL에 ?를 사용해 쿼리스트링을 추가하고, &로 여러 개의 쿼리스트링을 구분합니다.
  • url.parse() 메서드로 URL을 분석하고, querystring 모듈로 쿼리스트링을 분석합니다.

- 기본 구조

http://localhost:8080/?category=shoes&size=230
  • ? : 쿼리스트링의 시작
  • category=shoes : category가 shoes인 쿼리스트링
  • & : 쿼리스트링 구분자
  • size=230 : size가 230인 쿼리스트링

- url.parse() 메서드

  • url.parse() 메서드로 URL을 분석하면, URL의 여러 정보를 객체로 반환합니다.
  • url.parse(주소) : URL을 분석하여 URL 객체를 반환합니다.
  • url.parse(주소, true) : URL을 분석하여 URL 객체와 쿼리스트링 객체를 반환합니다.
// 이런 URL이 있다면
'http://localhost:8080/search?category=shoes&color=black&size=260'

// url.parse()로 분석하면 이렇게 분리됩니다
{
  protocol: 'http:',
  host: 'localhost:8080',
  pathname: '/search',
  query: 'category=shoes&color=black&size=260'
}

- querystring 모듈

  • querystring 모듈은 URL의 쿼리스트링을 객체로 변환하거나, 객체를 쿼리스트링으로 변환하는 모듈입니다.
  • querystring.parse(쿼리스트링) : 쿼리스트링을 객체로 변환합니다.
  • querystring.stringify(객체) : 객체를 쿼리스트링으로 변환합니다.
const querystring = require('querystring');
const parsedQuery = querystring.parse(parsedUrl.query);
// 이제 parsedQuery는 객체 형태가 됩니다
{
  category: 'shoes',
  color: 'black',
  size: '260'
}

- 실제 사용 예시

1) URL 주소에 쿼리스트링 추가

https://shop.com/?category=shoes&color=black&size=260

이는 다음을 의미합니다.

  • 카테고리 : 신발
  • 색상 : 검정
  • 사이즈 : 260
// querystring.js
const http = require('http');
const url = require('url');
// querystring 모듈을 불러옵니다.
const querystring = require('querystring');

const server = http.createServer((req, res) => {
    // URL 문자열을 URL 객체로 변환
    const parsedUrl = url.parse(req.url);
    // URL 객체에서 쿼리스트링을 객체로 변환
    const query = querystring.parse(parsedUrl.query);

    // 상품 검색 처리
    if (query.category || query.color || query.size) {
        // 검색 조건 메시지 생성
        let searchMessage = '<h2>검색 조건:</h2>';
        if (query.category) searchMessage += `<p>카테고리: ${query.category}</p>`;
        if (query.color) searchMessage += `<p>색상: ${query.color}</p>`;
        if (query.size) searchMessage += `<p>사이즈: ${query.size}</p>`;

        // 검색 결과 메시지 (실제로는 데이터베이스 조회 결과가 들어갈 부분)
        const resultMessage = `
            <h2>검색 결과:</h2>
            <div>
                <h3>상품명: ${query.color} ${query.category}</h3>
                <p>사이즈: ${query.size}</p>
                <p>가격: 89,000원</p>
            </div>
        `;

        // HTML 응답
        res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
        res.end(`
            <h1>쇼핑몰 상품 검색</h1>
            ${searchMessage}
            ${resultMessage}
        `);
    } else {
        // 검색 조건이 없을 때
        res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
        res.end(`
            <h1>상품을 검색해주세요</h1>
            <p>예시: /?category=shoes&color=black&size=260</p>
        `);
    }
});

// 서버 실행
server.listen(8080, () => {
    console.log('쇼핑몰 서버가 8080 포트에서 실행 중입니다.');
});

서버를 실행하고, 크롬의 주소창에 http://localhost:8080/?category=shoes&color=black&size=260를 입력해보세요.

0개의 댓글