본문 바로가기

BackEnd/Node

[노드교과서] 섹션 3. http 모듈로 서버 만들기. 섹션 4. 패키지 매니저

4.1. 요청과 응답 실행하기

 1. 서버와 클라이언트

  ⇒ 서버와 클라이언트의 관계

   - 클라이언트가 서버로 요청(request)를 보내면 서버는 해당 요청을 처리한다.

   - 처리된 결과를 서버가 클라이언트로 응답(response)을 보낸다.

 

 2. 노드로 http 서버 만들기

  ⇒ http 요청에 응답하는 노드 서버

   - createServer로 요청 이벤트에 대기

   - req 객체는 요청에 관한 정보를 담고 있고, res 객체는 응답에 관한 정보를 담고있다.

 

 1) http 서버 만들기

// server1.js

const http : require('http');

const server = http.createServer((req,res) => {
	// res는 스트림이다.
    // response의 header정보를 설정한다.
    res.writeHead({'Content-type' : 'text/html; charset=utf-8'});
    res.write('<h1>Hello Node</h1>');
    res.write('<p>Hello Server</p>');
    res.end('<p>Hello zerocho</p>');
})
	.listen(8085);   // 포트정보 설정

// 실행중일때 해당 내용이 수행된다.
server.on('listening',() => {
	console.log('8085포트에서 대기중');
});

// node에서는 error처리를 해줘야 한다.
// 프로세스 사망의 원인
server.on('error', (err) => {
	console.error(err);
});
// cmd에서 node server1 파일을 실행

cmd C:\work\005_study\http> node server
8085서버에서 접근 대기중

 

 

 2) fs로 HTML 파일을 읽어 제공하기

// server2.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Node.js 웹서버</title>
    </head>
    <body>
        <h1>Node.js 웹 서버</h1>
        <p>만들 준비되셨나요??</p>
    </body>
</html>

 

// server.js

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

// 비동기 함수는 try ~ catch로 예외처리를 해줘야 한다.
const server = server.createServer(async (req, res) => {
	try {
    	res.writeHead(200, {'Content-type' : 'text/html; charset=utf-8'});
        const data = await fs.readFile('./server2.html');
        res.end(data);
    } catch (err) {
    	console.error(err);
        // plain은 문자열을 의미
        res.writeHead(200, {'Content-type' : 'text/plain; charset-utf-8'});
        res.end(err.message);
    }
})
	.listen(8085);

server.on('listening',() => {
	console.log('8085서버에서 접근 대기중');
});
server.on('error', (error) => {
	console.error(error);
});
// cmd
cmd C:\work\005_study\http> node server2
8085서버에서 접근 대기중

 

5. localhost와 포트

  ⇒ localhost는 컴퓨터의 내부 주소이므로 외부에서는 접근이 불가능하다.

  ⇒ 포트는 서버 내에서 프로세스를 구분하는 번호이다.

   - 기본적으로 http는 80포트를 https는 443포트를 사용하며 주소창에서 해당 포트는 생략 가능하다.

   - 다른 포트를 통해 데이터베이스나 다른 서버를 동시에 연결이 가능하다.

    ex) 21(tfp), 23(telnet), 3306(mysql), 27017(MongoDB)

 

 

4.2. REST API 라우팅

 ⇒ 서버에서 요청을 보낼 때는 주소를 통해 요청의 내용을 표현한다.

  - /index.html이면 index.html을 보내달라는 뜻이다.

  - 항상 html을 요구할 필요는 없으며 서버가 이해하기 쉬운 주소가 좋다.

 ⇒ REST API(Representional State Transfer)

  - 서버의 자원을 정의하고 자원에 대한 주소를 지정하는 방법(방식)

  - 여기서 말하는 url을 동사에 속한다.

  - /user 일 경우 사용자 정보에 관한 정보를 요청하는 것

  - /post 일 경우 게시글에 관련된 자원을 요청하는 것

  ⇒ HTTP 요청 메서드

GET 서버 자원을 가져오라고 할 때 사용4  
POST 서버에 자원을 새로 등록하고자 할 때 사용  
PUT 서버에 자원을 요청에 들어오는 자원으로 치환 하고자 할때 사용 ex) 제로초라는 사람을 김재익으로 바꿈
PATCH 서버 자원의 일부만 수정하고자 할 때 사용 ex) 제로초라는 사람이 1살을 더 먹음
DELETE 서버의 자원을 삭제 하고자 할때 사용  

  ⇒ 클라이언트가 누구든 서버와 HTTP로 소통이 가능하다.

   - iOS, 안드로이드, 웹이 모두 같은 주소로 요청을 보낼 수 있다.

   - 서버와 클라이언트의 분리

  ⇒ RESTful

   - REST API를 사용한 주소 체계를 사용하는 서버를 RESTful한 서버라고 말한다.

    ex) GET /user 는 사용자를 조회하는 요청이다

    ex2) POST /user는 사용자를 등록하는 요청이다.

 

   - RESTful한 주소 구조

HTTP 메서드 주소 역할
GET / resFront.html 파일 제공
GET /about about.html 파일 제공
GET /users 사용자 목록 제공
GET 기타 기타 정적 파일 제공
POST /users 사용자 등록
PUT /users/사용자id 해당id의 사용자 수정
DELETE /users/사용자id 해당id의 사용자 제거

 

  - 개발자도구의 network에서 localhost정보 참조

   : Headers는 데이터들의 데이터라고 볼 수 있다.

    예를 들어 GET "/" 요청을 보냈을 경우 응답이 오면 그 html에 대한 데이터를 헤더로 담아서 보내준다.

    보내줄 때 html이라는 것을 알려주고 utf-8인것, 몇시에 응답한건지, 성공적(200)인지 등등을 알려준다.

    그 외에도 request response정보들이 다양하게 존재하므로 잘 살펴볼 필요성이 있다.

 

// REST API서버 중 GET 메서드를 통한 서버 구현

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

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

const server = http.createServer(async (req, res) => {
    try {
        if(req.method === 'GET') {
            if(req.url === '/') {
                const data = await fs.readFile(path.join(__dirname,'restFront.html'));
                res.writeHead(200, {'Content-Type' : 'text/html; chatset=utf-8;'});
                return res.end(data);
            } else if(req.url === '/about') {
                const data = await fs.readFile(path.join(__dirname,'about.html'));
                res.writeHead(200,{'Content-Type' : 'text/html; chatset=utf-8'});
                return res.end(data);
            } else if(req.url === '/users') {
                res.writeHead(200,{'Content-Type' : 'application/json; chatset=utf-8'});
                return res.end(JSON.stringify(users));
            } 
            // url이 '/' or '/about' or '/users' 아니면
            // 그 외의 GET 요청은 css, js파일을 요청하는 것
            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 = '';
                // request는 스트림이기 때문에 청크드를 모아서 body로 받는다.
                // 3.6.2절의 버퍼와 스트림에서 배웠던 readStream
                req.on('data', (data) => body += data);
                return req.on('end', () => {
                    console.log('POST 본문(Body):', body);
                    const { name } = JSON.parse(body);
                    const id = Date.now();
                    users[id] = name;
                    // 201 요청성공 and 리소스가 생성되었다.
                    res.writeHead(201, {'Content-Type' : 'text/plain; chatset=utf-8;'});
                    res.end(JSON.stringify(users));
               });
            }
        } else if(req.method == 'PUT') {
            // .startswith : /user/로 시작할 경우 true
            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;
                    res.writeHead(200, {'Content-Type' : 'text/plain; chatset=utf-8;'});
                    return res.end(JSON.stringify(users));
                });
            }
        } else if(req.method == 'DELETE') {
            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('DELETE 본문(BODY) : ' + body);
                    delete users[key];
                    res.writeHead(200, {'Content-Type' : 'text/plain; chatset=utf-8;'});
                    return res.end(JSON.stringify(users));
                });
            }
        }
        res.writeHead(404);
        return res.end('NOT FOUND');
    } catch (err) {
        console.error(err);
        res.writeHead(500, {'Content-Type' : 'text/plain; chatset=utf-8'});
        res.end(err.message);
    }
}).listen(8085, () => {
    console.log('8085번 포트에서 서버 대기중 입니다.');
});

server.on('error',(err) => {
    console.error(err);
});

 

// restFront.html

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="utf-8" />
  <title>RESTful SERVER</title>
  <link rel="stylesheet" href="./restFront.css" />
</head>
<body>
<nav>
  <a href="/">Home</a>
  <a href="/about">About</a>
</nav>
<div>
  <form id="form">
    <input type="text" id="username">
    <button type="submit">등록</button>
  </form>
</div>
<div id="list"></div>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="./restFront.js"></script>
</body>
</html>

 

// restFront.js

async function getUser() {
  try {
      // axios로 /users를 호출 해 users객체를 불러온다.
      const res = await axios.get('/users');
      const users = res.data;
      const list = document.getElementById('list');
      list.innerHTML = '';

      // 사용자마다 반복적으로 화면 표시 및 이벤트를 연결한다.
      Object.keys(users).map((key) => {
        const userDiv = document.createElement('div');
        const span = document.createElement('span');
        span.textContent = users[key];
        const edit = document.createElement('button');
        edit.textContent = '수정';

        // 수정버튼 클릭 이벤트
        edit.addEventListener('click', async () => {
          const name = prompt('바꿀 이름을 입력하세요');  // 이름 입력창 호출
          if(!name) {
            return alert('이름을 반드시 입력하셔야 합니다.');
          } 
          try {
            await axios.put('/user/' + key, { name });
            getUser();
          } catch(err) {
            console.error(err);
          }
        });
        const remove = document.createElement('button');
        remove.textContent = '삭제';
        remove.addEventListener('click', async() => {
          try {
            await axios.delete('/user/' + key);
            getUser();
          } catch (err) {
            console.error(err);
          }
        });

        userDiv.appendChild(span);
        userDiv.appendChild(edit);
        userDiv.appendChild(remove);
        list.appendChild(userDiv);
        console.log(res.data);
      });
  } catch(err) {
    console.log(err);
  }
}
window.onload = getUser;

document.getElementById('form').addEventListener('submit', async(e) => {
  // preventDefault는 submit의 기본동작을 방지한다.
  // form submit의 원시적인 기능을 정지한다고 보면 된다.
  e.preventDefault();

  const name = e.target.username.value;
  if(!name) {
    return alert('이름을 입력하세요');
  }

  try {
    await axios.post('/user', { name });
    getUser();
  } catch(err) {
    console.error(err);
  }

});

 

4.3 쿠키와 세션 이해하기

 1. 쿠키의 중요성

   ⇒ 요청(request)에는 한 가지 단점이 있다.  정확히 누가 요청을 했는지 알 수 없기 때문이다(IP, 브라우저 정보만 앎)

      해당 문제를 해소하기 위해서는 쿠키나 세션을 통해 로그인 처리를 해 사용자 정보를 담아 해소할 수 있다.

 

   ⇒ 쿠키는 키=값 이 한쌍의 형태로 이뤄져있다.  브라우저에 저장된 쿠키는 매 요청마다 서버에 동봉되어 보내지며

      쿠키를 읽어 사용자가 현재 누구인지 파악할 수 있다.

 

  2. 쿠키 서버 만들기

const http = require('http');

http.createServer((req, res) => {

	console.log(req.url, req.headers.cookie); // 요청url주소와 요청헤더의 쿠키 정보
    
    
    res.writeHead(200, {'Set-Cookie' : 'mycookie=zerocho'}); // 쿠키 파라미터 설정
    res.end('Hello Cookie');
})
	.listen(8085, () => {
    	console.log('8085 포트에서 대기중입니다.');
    });

 

  4. 헤더와 본문

   ⇒ http 요청과 응답은 헤더와 본문을 갖는다(개발자 도구에서 확인가능)

    - 헤더는 요청 또는 응답에 대한 정보를 갖고 있다.

    - 본문은 주고 받은 실제 데이터를 갖고 있다.

    - 쿠키는 부가적인 정보이므로 헤더에 저장되어 있다.

 

 5. http 상태 코드

   ⇒ http.writeHead 메서드에 첫번째 인수로 넣는 값은 브라우저에게 보내는 상태코드이다.

    - 2XX : 성공을 알리는 코드이다.  200(성공), 201(작성됨)을 많이 사용한다.

    - 3XX : 리다이렉션(다른페이지이동)을 알리는 데 사용한다.  어떤 주소를 입력 했는데 다른 주소로 넘어갈 때

              이 코드가 사용된다.  300(영구이동), 301(임시이동)이 있다.

    - 4XX : 요청 자체에 오류가 있을때 표기되며 401(권한없음), 403(금지), 404(찾을 수 없음)이 있다.

    - 5XX : 서버 오류를 나타내며 욫어은 정상적이나 서버에 오류가 났을 때 발생한다.  이 오류를 클라이언트로 사용자가

              직접 보내는 경우는 없고, 예기치 못한 에러 발생 시 서버가 5XX코드로 보내준다.

              500(내부서버오류), 502(불량게이트웨이), 503(서비스를사용할수없음)이 자주 사용된다.

 

  6. 쿠키로 내 정보를 입력

// cookie2.js

const http = require('http');
const fs = require('fs').promise;
const url = require('url');
const qs = require('querystring');

// parseCookies : 쿠키 문자열을 객체로 변환한다.
const parseCookies = (cookie = '') =>
  cookie
    .split(';')
    .map(v => v.split('='))
    .reduce((acc, [k, v]) => {
      acc[k.trim()] = decodeURIComponent(v);
      return acc;
    }, {});

http.createServer(async (req, res) => {
	const cookies = parseCookies(req.headers.cookie);
    
    /login인 경우 쿼리스트링으로 온 이름을 쿼리로 저장.
    if(req.url.startsWith('/login') {
    	const { query } = url.parse(req.url);
        const { name } = qs.parse(query);
        const expries = new Date();
        expries.setMinutes(expries.getMinutes() + 5);

        // url의 시작이 /login이 포함되어 있는 경우
        // encodeURIComponent : URI로 데이터를 전달하기 위해 문자열 인코딩처리
        // Exprires : 쿠키의 만료시간을 설정할 수 있다.
        // Path : 설정한 패스 하위로 해당 쿠키를 사용할 수 있다.
        // HttpOnly : 자바스크립트로 쿠키에 접근할 수 없도록 처리한다.
        res.writeHead(302, {
        	Location : '/'
            'Set-Cookie' : `name=${encodeURIComponent(name)}; Expries=${expries.toGMTString()}; HttpOnly; Path=/',
        });
        res.end(name);
        
    // cookie에 name이존재할 경우
    // HTTP Only Cookie가 활성화 되어 XSS같은 공격을 차단한다.
    } else if(cookies.name) {
    	res.writeHead(200, {'Content-Type' : 'text/plain; charset=utf-8;'});
    	res.end(`${cookies.name}님 안녕하세요`);
    } else {
    	try {
            	const data = await fs.readFile('./cookie2.html');
                res.writeHead(200, {'Content-Type' : 'text/html; charset=utf-8'};
                res.end(data);
        } catch (err) {
        	res.writeHead(500, {'Content-Type' : 'text/plain; charset=utf-8'});
            res.end(err.message);
        }

    }
});

 

 7. 쿠키옵션

  ⇒ Set-Cookie의 다양한 옵션

   - 쿠키명=쿠키값 : 기본적인 쿠키의 값이다.  mycookie=test 또는 name=zerocho 같이 설정한다.

   - Expires=Date : 쿠키 만료 기한을 설정할 수 있다.  설정할 때는 Date 객체를 넣는다.

   - Max-age=초 : Expires와 기능은 동일하나 날짜 대신 초로 만료기한을 설정할 수 있다.

   - Domain=도메인명 : 쿠키가 전송될 도메인을 특정할 수 있다.  기본 값은 현재 도메인이다.

   - Path=URL : 쿠키가 전송될 URL을 특정할 수 있다.  기본 값은 '/'이고 이 경우 모든 url에서 쿠키를 전송할 수 있다.

    - HttpOnly : 설정 시 자바스크립트에서 쿠키를 접근할 수 없다.  쿠키 조작을 방지하기 위해 사용한다.

 

 9. 세션 사용하기

  ⇒ 쿠키의 정보는 클라이언트(브라우저)에 노출되어 수정되는 위험이 있다.

   - 중요한 정보는 서버에서 관리하고 클라이언트(브라우저)에는 세션 키(uniqueKey)만을 제공하면 된다.

   - 서버에 세션 객체(session)생성 후, uniqueInt(키)를 만들어 속성명으로 사용한다.

   - 속성 값에 정보를 저장하고 uniqueInt를 클라이언트(브라우저)로 보낸다.

const http = require('http');
const fs = require('fs').promises;
const url = require('url');
const qs = require('querystring');

const parseCookies = (cookie = '') =>
  cookie
    .split(';')
    .map(v => v.split('='))
    .reduce((acc, [k, v]) => {
      acc[k.trim()] = decodeURIComponent(v);
      return acc;
    }, {});
    
// 세선정보를 저장하는 공간
const session = {};

http.createServer(async (req, res) => {
	const cookie = parseCookies(req.headers.cookie);
    
    if(req.url.startsWith('/login')) {
    	const { query } = url.parse(req.url);
        const { name } = qs.parse(query);
        const expires = new Date();
        expires.setMinutes(qxpires.getMinutes() + 5);
        const uniqueInt = Date.now();
        
        // 서버 내에서만 name, expires 속성정보를 확인 가능하다.
        // 쿠키에는 세션의 키(uniqueInt)만 전달한다.
        session[uniqueInt] = {
        	name,
            expires,
        };
        
        res.writeHead(302, {
        	Location : '/',
            'Set-Cookie' : `session=${uniqueInt}; Expires=${expires.toGMTString()}; HttpOnly; Path=/`,
        });
        res.end();
    // 세션이 있거나 세션만료일시가 지나지 않았을 경우(크로스체크)    
    } else if(cookie.session && session[cookie.session].expires > new Date()) {
    	res.writeHead(200, {'Content-Type' : 'text/plain; charset=utf-8'});
        res.end(`${session[cookies.session].name}님 안녕하세요`);
    } else {
    	try {
            const data = await fs.readFile('./cookie2.html');
            res.writeHead(200, {'Content-Type' : 'text/html; charset=utf-8;'});
            res.end();
        } catch(err) {
        	res.writeHead(500, {'Content-Type' : 'text/plain; charset=utf-8'});
            res.end(err.message);
        }
	}
});

4.4 https와 http2

 1. https

  ⇒ 웹 서버에 SSL 암호화를 추가하는 모듈이며 요청/응답 간 데이터를 암호화 처리한다.

   - 최근에는 개인정보에 대한 보안이 필수이므로 https는 기본적으로 사용된다.

  2. https서버

  ⇒ http서버를 https로 변환하고 자 할 때에는 암호화를 인증서를 발급 받아야 한다.

   - 인증서 무료기관 : https://letsencrypt.org/ko/

 

// https.js

const https = require('https');
const fs = require('fs');

// readFileSync는 동기 메서드이므로 최초 초기화 할때 사용하기 유용하다.
// https는 인증서 정보를 넘겨줄 매개변수가 하나 더 추가된다.
https.createServer({
	cert : fs.readFileSync('도메인 인증서 경로'),
    key : fs.readFileSync('도메인 비밀키 경로'),
    ca : [
    	fs.readFileSync('상위 인증서 경로'),
        fs.readFileSync('상위 인증서 경로'),
    ],
},(req, res) => {
	res.writeHead(200, { 'Content-Type' : 'text/html; charset=utf-8'});
    res.write('<h1>Hello Node!</h1>');
    res.end('<p>Hello Server!</p>');
})
	.listen(443, () => {
		console.log('443 포트에서 대기중입니다.');
	});

 3. http2

  ⇒ SSL 암호화와 더불어 최신 HTTP 프로토콜인 http/2를 사용하는 모듈이다.

   - 요청 및 응답 방식이 기존 http/1.1보다 개선되어 웹의 속도가 향상 되었다.

   - 자원들의 동시성을 늘려 여러 개의 요청을 한번에 처리하여 빠르다.

const http2 = require('http2');
const fs = require('fs');

// http2는 ssl과 동일하나 서버를 생성하는 메서드가 다르다.
// createSecureServer
http2.createSecureServer({
	cert : fs.readFileSync('도메인 인증서 경로'),
    key : fs.readFileSync('도메인 비밀키 경로'),
    ca : [
    	fs.readFileSync('상위 인증서 경로'),
        fs.readFileSync('상위 인증서 경로'),
    ],
}, (req, res) => {
	res.writeHead(200, {'Content-Type' : 'text/html charset-8'});
    res.write('<h1>Hello Node!</h1>');
    res.end('<p>Hello Server!</p>');
})
	.listen(443, () => {
    	console.log('443번 포트에서 대기중입니다');
	});

4.5 cluster

 1. cluster

  ⇒ 노드는 기본적으로 싱글 스레드이기 때문에 CPU코어를 하나만 사용하지만 CPU코어를 모두 사용할 수

     있게 해주는 모듈이다.

   - 포트를 공유하는 노드 프로세스를 여러 개 둘 수 있다.

   - 요청이 많이 들어왔을 때 병렬로 실행된 서버의 개수만큼 요청이 분산되기 때문에 서버에 무리가 덜 간다.

   - 코어가 8개인 서버가 있을 때 노드는 코어 하나만 활용하지만, cluster로 코어 하나당 노드 프로세스를

    하나씩 배정할 수 있어 성능이 8배가 되는건 아니지만 병렬 처리이기 때문에 대폭 향상된다.

   - 단점은 컴퓨터자원(메모리, 세션 등)에 대한 공유가 안되므로 Redis 등 별도의 서버로 해결해야 한다.

const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length; // 현재 PC의 CPU코어수를 얻어온다.

if(cluster.isMaster) {
	console.log(`마스터 프로세스 아이디 : ${process.pid}`);
    
    // cpu개수만큼 워커 프로세스를 생성
    for (let i = 0; i < numCPUs; i++) {
    	cluster.fork();
    }
    
    // 워커가 종료 되었을 경우
    // 에러로 워커가 종료 되었을 경우에도 서버를 다시 살리는 로직을 구성해야 한다.
    // 실제 운영할 때에도 이러한 형태는 아니지만 서버를 지속적으로 부활시킨다.
    cluster.on('exit', (worker, code, signal) => {
    	console.log(`${worker.process.pid}번 워커가 종료 되었습니다.`);
        console.log(`code : ${code}, signal : ${signal}`);
        cluster.fork();
    });
} else {
	// 워커들이 포트에서 대기
    // 로컬호스트에 접근하면 setTimeout을 이용하여 1초 후 프로세스 하나를 강제로 종료시킨다.
    http.createServer((req, res) => {
    	res.writeHead(200, {'Content-Type' : 'text/html; charset=utf-8'});
        res.write('<h1>Hello Node!</h1>');
        res.end('<p>Hello Cluster!</p>');
        setTimeout(() => {
        	process.exit(1);
        },1000);
    })
    	.listen(8085, () => {
        	console.log('8085 포트에서 대기중입니다');
    	});
        
        console.log(`${process.pid}번 워커 실행`);
}

  ⇒ 마스터 프로세스와 워커 프로세스

   - 마스터 프로세스는 CPU  개수만큼 워커 프로세스를 만듦(worker_threads랑 구조가 비슷)

   - 요청이 들어오면 워커 프로세스에 고르게 분배된다.

5.1 npm 알아보기

 1. npm이란

  ⇒ Node Package Manager

   - 노드의 패키지 매니저이며 다른 사람들이 만든 소스들을 모아둔 저장소이다.

   - 남의 코드를 사용하여 프로그래밍이 가능하기 때문에 직접 구현할 필요가 없어 효율적이다.

   - 오픈 소스 생태계를 구성중이다.

   - 패키지 : npm에 업로드된 노드 모듈

   - 모듈이 다른 모듈을 사용할 수 있듯 패키지도 다른 패키지를 사용할 수 있다(의존관계)

 2.  package.json 속성들

  ⇒ package name : 패키지의 이름

  ⇒ version : 패키지의 버전이며 npm의 버전은 다소 엄격하게 관리되고 있다.

  ⇒ entry point

   : 자바스크립트 실행 파일의 진입점이다.  보통 마지막으로 module.exports를 하는 파일을 지정한다.

    package.json의 main 속성에 지정한다.

  ⇒ test comand

   : 코드를 테스트 할 때 입력할 명령어를 의미한다.  package.json scripts 속성 안에 test 속성에 저장된다.

  ⇒ git repository

   : 코드를 저장해 둔 Git 저장소 주소를 의미한다.  추후 소스에 문제가 발생 시 사용자들이 저장소에 들러

    이의를 제기할 수 있고, 코드 수정본을 직접 올릴 수도 있다.  package.json의 repository 속성에 저장된다.

  ⇒ keyword : 키워드는 npm 공식 홈페이지(https://npmjs.com)에서 패키지를 쉽게 찾을 수 있게 해준다.

  ⇒ license : 해당 패키지의 라이센스를 넣어주면 된다 (ex) MIT)

               

{
  "name": "npmtest",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "JaeikKim",
  "license": "MIT",
  "dependencies": {
    "body-parser": "^1.20.2",
    "cookie-parser": "^1.4.6",
    "express": "^4.18.2"
  },
  "devDependencies": {
    "nodemon": "^3.0.2",
    "rimraf": "^5.0.5"
  }
}

 5. node_modules

  ⇒ npm install 시 node_modules 폴더가 생성된다.

   - 내부에 설치한 패키지들이 들어있다.

   - express 패키지를 dependency했을 때 해당 패키지 외에 의존관계인 타 패키지들도 함께 설치된다.

  ⇒ package-lock.json에서 express의 패키지간 의존관계를 명확하게 확인할 수 있다.

 6. 여러 패키지 동시에 설치하기

  ⇒ npm i cookie-parser body-parser -D

 7. 개발용 패키지로 패키지 설치하기

  ⇒ npm i nodeman -D

 8. 글로벌(전역) 패키지

  ⇒ npm i -g 패키지명

   - 모든 프로젝트와 콘솔에서 패키지를 실행할 수 있지만 dependency에 명시되지 않는 단점이 있어 권장하지 않는다.

   - 디펜던시 개발용 패키지에 포함시켜 'npx 패키지명 명령어' 로 글로벌 설치 없이 명령어 사용이 가능하다.

5.3 패키지 버전 이해하기

 1. SemVer 버저닝

  ⇒ 노드 패키지의 버전은 SemVar(유의적 버저닝) 방식을 따른다.

   - Major(주 버전), Minor(부 버전), Patch(수 버전)

   - 노드에서는 배포를 할 때 항상 버전을 올려야 한다.

   - Major는 하위 버전과 호환하지 않은 수정사항이 생겼을 때 올린다.

   - Minor는 하위 버전과 호환되는 수정사항이 생겼을 때 올린다.

   - Patch는 기능에 버그를 해결했을 때 올린다.

 2. 버전 기호 사용하기

  ⇒ 버전 앞에 기호를 붙여 의미를 더한다.

   - ^1.1.1 : 패키지 업데이트 시 minor 버전까지만 업데이트가 되도록 고정한다(2.0.0버전은 안됨)

   - ~1.1.1 : 패키지 업데이트 시 patch 버전까지만 업데이트가 되도록 고정한다.(1.2.0버전은 안됨)

   - >=, <=, >, < 는 이상, 이하, 초과, 미만을 의미한다.

   - @latest는 최신을 의미하며 패키지 버전을 @3.2.1 이런식으로 명시해서 받을 수도 있다.

   - @next로 정식 배포판이 아닌 가장 최신 배포판을 사용해볼 수도 있다.(불안정)

   - 알파/베타/RC 버전이 존재할 수도 있다.(1.1.1-alpha.0, 2.0.0-beta.1, 2.0.0-rc.0)

 

5.4 기타명령어

 1. 기타 명령어

  ⇒ 명령어는 수정되는 경우가 있어 https://docs.npmjs.com/cli/v10/commands 에서 확인하는 편이 좋다.

  ⇒ npm outdated

   : 어떤 패키지에 기능 변화가 생겼는지 알 수 있다.  Current는 현재 버전이며 Wanted까지는 'npm update' 명령어를 통해 

    업데이트가 자동으로 된다.  Latest 메이저가 변경되는 경우이며 직접 확인하고 업데이트 처리해야 한다.

   

노드교과서

  ⇒ npm uninstall 패키지명 : 패키지 삭제(npm rm 패키지명으로도 가능하다)

  ⇒ npm search 검색어 : npm패키지를 검색할 수 있다(npmjs.com에서도 가능)

  ⇒ npm info 패키지명 : 패키지의 세부 정보 파악 가능

  ⇒ npm adduser : npm에 로그인을 하기 위한 명령어(npmjs.com에서 회원가입)

  ⇒ npm whoami : 현재 로그인 된 계정이 어떤 계정인지 알려준다.

  ⇒ npm logout : 로그인한 계정을 로그아웃 처리한다.

  ⇒ npm version 버전

   : package.json 버전을 올린다.  이 명령어를 통해 버전을 올리는 이유는 git도 동시에 commit 되어

    버전관리를 처리해주기 때문이다.

   ex) 'npm version major', 'npm version minor', 'npm version patch' 각 버전단위 1씩 올릴 수 있다.

  ⇒ npm deprecate [패키지명][버전] [메시지]

   : 패키지를 설치할 때 경고 메시지를 띄우게 함(오류가 있는 패키지에 적용)

  ⇒ npm publish : 자신이 만든 패키지를 배포한다.

  ⇒ npm unpublish --force : 자신이 만든 패키지를 배포 중단한다(배포 후 72시간 내에만 가능)

  ⇒ npm ls 패키지명 : 해당 패키지가 있는지, 해당 패키지가 어떤 패키지의 dependencies인지 보여준다.