서버로서의 노드

 >> 서버 : 네트워크를 통해 클라이언트에 정보나 서비스를 제공하는 컴퓨터 또는 프로그램

 >> 클라이언트 : 서버에 요청을 보내는 주체(브라우저, 데스크탑 프로그램, 모바일 앱, 다른 서버에 요청을 보내는 서버)

 >> 예시

   - 브라우저(클라이언트, 요청)가 길벗 웹사이트(서버, 응답)에 접속

   - 핸드폰(클라이언트)을 통해 앱스토어(서버)에서 앱 다운로드

 >> 노드 != 서버

 

 >> 노드서버의 장단점

장점 단점
멀티 스레드 방식에 비해 컴퓨터 자원을 적게 사용함 싱글 스레드라서 CPU 코어를 하나만 사용함
I/O 작업이 많은 서버로 적합 CPU 작업이 많은 서버로는 부적합
멀티 스레드 방식보다 쉬움 하나뿐인 스레드가 멈추지 않도록 관리해야 함
웹 서버가 내장되어 있음 서버 규모가 커졌을 때 서버를 관리하기 어려움
자바스크립트를 사용함 어중간한 성능
JSON 형식과 호환하기 쉬움  

 

 >> CPU 작업을 위해 AWS Lambda나 Google Cloud Functions 같은 별도 서비스 사용

 >> 페이팔 , 넷플리스, 나사 , 월마트, 링크드인, 우버 등에서 메인 또는 서브 서버로 사용

 

 

서버 외의 노드 

 >> 자바스크립트 런타임이기 때문에 용도가 서버에만 한정되지 않음

 >> 웹, 모바일, 데스크탑 애플리케이션에도 사용

  - 웹 프레임워크 : Angular, React, Vue, Meteor 등

  - 모바일 앱 프레임워크 : React Native

  - 데스크탑 개발 도구 : Electron(Atom, Slack, VSCode, Discord 등 제작)

 

 

 

'Javascript > Node' 카테고리의 다른 글

var const let 자바스크립트 변수 상수  (0) 2024.10.30
Node.js 다운로드 설치 최신버전 22버전  (0) 2024.10.29
노드의 특성  (0) 2024.10.27
노드의 정의  (0) 2024.10.26
노드 교과서 섹션0  (0) 2023.09.07

1. 이벤트 기반

 >> 이벤트가 발생할 때 미리 지정해둔 작업을 수행하는 방식

   - 이벤트의 예 : 클릭, 네트워크 요청, 타이머 등

   - 이벤트 리스너  : 이벤트를 등록하는 함수

   - 콜백 함수 : 이벤트가 발생했을 때 실행될 함수

 

2. 논블로킹 I/O

 >> 논 블로킹 : 오래 걸리는 함수를 백그라운드로 보내서 다음 코드가 먼저 실행되게 하고, 나중에 오래 걸리는 함수를 실행 

   - 논 블로킹 방식 하에서 일부 코드는 백그라운드에서 병렬로 실행됨

   - 일부 코드 : I/O 작업(파일 시스템 접근, 네트워크 요청), 압축, 암호화 등

   - 나머지 코드는 블로킹 방식으로 실행됨

   - I/O 작업이 많을 때 노드 활용성이 극대화 

 

3. 프로세스 vs 스레드

 >> 프로세스와 스레드

  - 프로세스 : 운영체제에서 할당하는 작업의 단위, 프로세스 간 자원 공유 X

  - 스레드 : 프로세스 내에서 실행되는 작업의 단위, 부모 프로세스 자원 공유

 

 >> 노드 프로세스는 멀티 스레드이지만 직접 다룰 수 있는 스레드는 하나이기 때문에 싱글 스레드라고 표현

 

 >> 노드는 주로 멀티 스레드 대신 멀티 프로세스 활용

 

 >>  노드는 14버전부터 멀티 스레드 사용 가능

 

4. 싱글 스레드

 >> 싱글 스레드라 주어진 일을 하나밖에 처리하지 못함

   -  블로킹이 발생하는 경우 나머지 작업은 모두 대기해야 함  -> 비효율 발생

 

>> 주방에 비유 ( 점원 : 스레드 , 주문 : 요청, 서빙 : 응답)

 

>> 노드는 논 블로킹 모델을 채택하여 일부 코드(I/O)를 백그라운드(다른 프로세스)에서 실행 가능

  - 요청을 먼저 받고, 완료될 때 응답함

  - I/O 관련 코드가 아닌 경우 싱글 스레드, 블로킹 모델과 같아짐

 

 

'Javascript > Node' 카테고리의 다른 글

Node.js 다운로드 설치 최신버전 22버전  (0) 2024.10.29
서버 노드  (0) 2024.10.28
노드의 정의  (0) 2024.10.26
노드 교과서 섹션0  (0) 2023.09.07
노드 교과서 섹션 1  (0) 2023.09.06

Node.js는 크롬 V8 자바스크립트 엔진으로 빌드된 자바스크립트 런타임입니다.

 - 서버의 역할도 수행할 수 있는 자바스크립트 런타임

 - 노드는 자바스크립트로 작성된 서버를 실행할 수 있음.

 - 서버 실행을 위해 필요한 http/https/http2 모듈을 제공

 

노드 : 자바스크립트 런타임

 - 런타임 : 특정 언어로 만든 프로그램을 실행할 수 있게 해주는 가상 머신(크롬의 V8 엔진사용)의 상태

 - 노드 : 자바스크립트로 만든 프로그램들을 실행할 수 있게 해 줌

 - 다른 런타임으로는 웹 브라우저(크롬, 엣지, 사파리, 파이어폭스 등)가 있음

 

노드는 V8과 libuv를 내부적으로 포함

 - V8 엔진 :오픈 소스 자바스크립트 엔진 -> 속도 문제 개선

 - libuv : 노드의 특성인 이벤트 기반, 논블로킹 I/O 모델을 구현한 라이브러리

 

 

'Javascript > Node' 카테고리의 다른 글

서버 노드  (0) 2024.10.28
노드의 특성  (0) 2024.10.27
노드 교과서 섹션0  (0) 2023.09.07
노드 교과서 섹션 1  (0) 2023.09.06
노드 교과서 섹션 2  (0) 2023.09.05

자바스크립트는 스크립트 언어라서 즉석에서 코드를 실행할 수 있음

REPL : R(Read), E(Evaluate) , P(Print), L(Loop)

실행방법 :  node helloworld.js

export , import 방법

 

//var.mjs
export const odd = "MJS 홀수입니다."
export const even = "MJS 짝수입니다."

 

//func.mjs
import {odd , even} from './var.mjs';

function checkOddEven(num){
    if (num % 2){
        return odd ;
    }
    return even;
}

export default checkOddEven;
 

 

//index.mjs
import {odd, even} from './var.mjs';
import checknumber from './func.mjs';

function checkStringOddEven(str){
    if (str.length % 2){
        return odd ;
    }
    return even;
}

console.log(checknumber(10));
console.log(checkStringOddEven('hello'));

 

 

//process 실행결과

> process.version
'v20.16.0'
> process.uptime()
23.5491616
> process.cwd()
'D:\\code\\node'

//환경변수는 process.env 로 접근 가능
const secretId = process.env.SECRET_ID;
const secretCode = process.env.SECRET_CODE;



// path.js
const path = require('path');

console.log(path.join(__dirname,'/var.js'));
console.log(path.resolve(__dirname,'/var.js')); // 절대 경로가 있으면 앞에가 무시됨

 

//수행 결과
D:\code\node>node path.js
D:\code\node\var.js
D:\var.js

 

//readmefiles.js
const fs = require('fs').promises;

fs.readFile('./readme.txt')
    .then((data)=> {
        console.log(data);
        console.log(data.toString());
    })
    .catch((err)=>{
        throw err;
    });

 

//수행결과
D:\code\node>node readmefiles.js
<Buffer ec a0 80 eb a5 bc 20 ec 9d bd ec 9c bc ec 84 b8 ec 9a 94 2e>
저를 읽으세요.

 

>> 버퍼 : 일정한 크기로 모아두는 데이터

- 일정한 크기가 되면 한 번에 처리
- 버퍼링 : 버퍼에 데이터가 찰 때까지 모으는 작업

>> 스트림 : 데이터의 흐름

- 일정한 크기로 나눠서 여러 번에 걸쳐서 처리
- 버퍼(또는 청크)의 크기를 작게 만들어서 주기적으로 데이터를 전달
- 스트리밍 : 일정한 크기의 데이터를 지속적으로 전달하는 작업

 

//buffer.js
const buffer = Buffer.from('저를 버퍼로 바꿔보세요.');
console.log(buffer);
console.log(buffer.length);
console.log(buffer.toString());

const array = [Buffer.from('띄엄 '), Buffer.from('띄엄 '), Buffer.from('띄어쓰기')];
console.log(Buffer.concat(array).toString());

console.log(Buffer.alloc(5));

//실행 결과
D:\code\node>node buffer.js
<Buffer ec a0 80 eb a5 bc 20 eb b2 84 ed 8d bc eb a1 9c 20 eb b0 94 ea bf 94 eb b3 b4 ec 84 b8 ec 9a 94 2e>
33
저를 버퍼로 바꿔보세요.
띄엄 띄엄 띄어쓰기
<Buffer 00 00 00 00 00>

 

//createReadStream.js
const fs = require('fs');
const readStream = fs.createReadStream('./readme3.txt',{highWaterMark : 16});

const data = [];
readStream.on('data',(chunk)=>{
    data.push(chunk);
    console.log('data',chunk, chunk.length);
});
readStream.on('end',()=>{
    console.log('end:', Buffer.concat(data).toString());
});
readStream.on('error',(err)=>{
    console.log('error:', err);
});

//실행결과
D:\code\node>node createReadStream.js
data <Buffer ec a0 80 eb 8a 94 20 ec a1 b0 ea b8 88 ec 94 a9> 16
data <Buffer 20 ec a1 b0 ea b8 88 ec 94 a9 20 eb 82 98 eb 88> 16
data <Buffer a0 ec 84 9c 20 ec a0 84 eb 8b ac eb 90 a9 eb 8b> 16
data <Buffer 88 eb 8b a4 2e 20 eb 82 98 eb 88 a0 ec a7 84 20> 16
data <Buffer ec a1 b0 ea b0 81 ec 9d 84 20 63 68 75 63 6b eb> 16
data <Buffer 9d bc ea b3 a0 20 eb b6 80 eb a6 85 eb 8b 88 eb> 16
data <Buffer 8b a4 2e 0d 0a ec 95 88 eb 85 95 ed 95 98 ec 84> 16
data <Buffer b8 ec 9a 94 2e 20 ed 97 ac eb a1 9c 20 eb 85 b8> 16
data <Buffer eb 93 9c 20 ed 97 ac eb a1 9c 20 ec 8a a4 ed 8a> 16
data <Buffer b8 eb a6 bc 20 ed 97 ac eb a1 9c 20 eb b2 84 ed> 16
data <Buffer 8d bc> 2
end: 저는 조금씩 조금씩 나눠서 전달됩니다. 나눠진 조각을 chuck라고 부릅니다.
안녕하세요. 헬로 노드 헬로 스트림 헬로 버퍼

 

//createWriteStream.js
const fs = require('fs');
const { writeHeapSnapshot } = require('v8');

const writeStream = fs.createWriteStream('./writeme2.txt');
writeStream.on('finish',()=>{
    console.log('파일 쓰기 완료');
});

writeStream.write('이 글을 씁니다.\n');
writeStream.write('한 번 더 씁니다.');
writeStream.end();

//실행결과
D:\code\node>node createWriteStream.js
파일 쓰기 완료

//writeme2.txt 가 생성 되고 내용 확인 가능

 

>> 예외 처리

- 노드 스레드를 멈춤
- 노드는 기본적으로 싱글 스레드라 스레드가 멈춘다는 것은 프로세스가 멈추는 것
- 에러 처리는 필수

>> 기본적으로 try catch문으로 예외를 처리

- 에러가 발생할 만 곳을 try catch로 감쌈

//error.js

setInterval(()=>{
    console.log('시작');
    try {
        throw new Error('서버를 고장낸다');
    }catch(err){
        console.error(err);
    }
}, 1000);

// 실행결과
D:\code\node>node error.js
시작
Error: 서버를 고장낸다
    at Timeout._onTimeout (D:\code\node\error.js:4:15)
    at listOnTimeout (node:internal/timers:581:17)
    at process.processTimers (node:internal/timers:519:7)
시작
Error: 서버를 고장낸다
    at Timeout._onTimeout (D:\code\node\error.js:4:15)
    at listOnTimeout (node:internal/timers:581:17)
    at process.processTimers (node:internal/timers:519:7)
^C

'Javascript > Node' 카테고리의 다른 글

노드 교과서 섹션0  (0) 2023.09.07
노드 교과서 섹션 1  (0) 2023.09.06
노드 교과서 섹션 3  (0) 2023.09.01
노드 교과서 섹션 4  (0) 2023.08.31
노드 교과서 섹션 5  (0) 2023.08.29

서버와 클라이언트의 관계

  • 클라이언트가 서버로 요청(request)을 보냄
  • 서버는 요청을 처리
  • 처리 후 클라이언트를 응답(response)을 보냄

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

  • createServer로 요청 이벤트에 대기
  • req 객체는 요청에 관한 정보가, res 객체는 응답에 관한 정보가 담겨 있음

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

  • https 는 기본 port 443 (생략할 수 있다.)
  • http 는 기본 port 80 (생략할 수 있다.)
  • 예) www.github.com:80www.github.com
  • 다른 포트로 데이터베이스나 다른 서버 동시에 연결 가능
//server1.js

const http = require('http');

const server = http.createServer((req, res) => {
    res.writeHead(200, { '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(8080);
server.on('listening',()=>{
    console.log('8080번 포트에서 서버 대기 중입니다.');
});
server.on('error', (error)=> {
    console.error(error);
})

 

localhost:8080 으로 접속하면 아래와 같은 결과를 얻을 수 있다.

Hello Node!

Hello server

Hello ZeroCho

 

코드를 수정하였으면 한 번 껐다가 켜 주어야 수정이 된다.

포트 다르게 하면 동시에 서버를 추가로 기동할 수 있다.

 

아래와 같이 html 파일을 읽어서 서버를 기동할 수도 있다.

 

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

//server2.js
const http = require('http');
const fs = require('fs').promises;

const server = http.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(error){
        console.error(error);
        res.writeHead(200, {'Content-Type':'text/plain; charset=utf-8'});
        res.end(error.message);
       
    }
   
}).listen(8080);
server.on('listening',()=>{
    console.log('8080번 포트에서 서버 대기 중입니다.');
});
server.on('error', (error)=> {
    console.error(error);
})

 

localhost:8080 으로 접속하면 아래와 같은 결과를 얻을 수 있다.

Noe.js 웹 서버

만들 준비 되셨나요?

REST API(Representational State Transfer)

  • 서버의 자원을 정의하고 자원에 대한 주소를 지정하는 방법
  • /user이면 사용자 정보에 관한 정보를 요청하는 것
  • /post 면 게시글에 관련된 자원을 요청하는 것

HTTP 요청 메서드

  • GET : 서버 자원을 가져오려고 할 때 사용
  • POST : 서버에 자원을 새로 등록하고자 할 때 사용(또는 뭘 써야할 지 애매할 때)
  • PUT : 서버의 자원을 요청에 들어있는 자원으로 치환하고자 할 때 사용
  • PATCH : 서버 자원의 일부만 수정하고자 할 때 사용
  • DELETE : 서버의 자원을 삭제하고자 할 때 사용

서버 주소 뒤에 아무 것도 입력하지 않으면 GET 요청을 보내겠다는 뜻이다.

HTTP 상태 코드 에서 201은 생성 되었다는 의미가 있다.

 

GET, POST, PUT, DELETE 요청 보내기

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

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

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; charset=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; charset=utf-8' });
        return res.end(data);
      } else if (req.url === '/users') {
        res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
        return res.end(JSON.stringify(users));
      }
      // /도 /about도 /users도 아니면
      try {
        const data = await fs.readFile(path.join(__dirname, 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, { 'Content-Type': 'text/plain; charset=utf-8' });
          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;
          res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
          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];
        res.writeHead(200, { 'Content-Type': 'text/plain; charset=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; charset=utf-8' });
    res.end(err.message);
  }
})
  .listen(8082, () => {
    console.log('8082번 포트에서 서버 대기 중입니다');
  });

 

<쿠키의 필요성>

요청에는 한 가지 단점이 있음

  • 누가 요청을 보냈는지 모름(IP 주소와 브라우저 정보 정도만 앎)
  • 로그인을 구현하면 됨
  • 쿠키와 세션이 필요

쿠키 : 키=값의 쌍

  • name=zerocho
  • 매 요청마다 서버에 동봉해서 보냄
  • 서버는 쿠키를 읽어 누구인지 파악

쿠키 넣는 것을 직접 구현

  • writeHead: 요청 헤더에 입력하는 메서드
  • set-Cookie : 브라우저에게 쿠키를 설정하라고 명령

처음에 접속할 때 set-Cookie 를 통해서 쿠키를 생성한 후에

새로고침을 하게 되면 쿠키를 갖고 접속하게 된다.

개발자도구 Network 탭에서 Request Headers 에서 Cookie 에 입력되어서 브라우저에서 보내준다.

(처음에는 없었음)

 

//cookie.js
const http  = require('http');

http.createServer((req, res)=>{
    console.log(req.url, req.headers.cookie);
    res.writeHead(200, {'Set-Cookie':'mycookie=test'});
    res.end('Hello Cookie');
})
.listen(8083, ()=>{
    console.log('8083번 포트에서 서버 대기 중입니다.')
})

//실행 결과 값
D:\code\node\4.3>node cookie.js
8083 포트에서 서버 대기 중입니다.
/ undefined   <-- 처음은 쿠키가 없음
/favicon.ico mycookie=test
/ mycookie=test <-- 새로고침 하면 쿠키가 입력되어서 오게 된다.
/favicon.ico mycookie=test

 

 

http 요청과 응답은 헤더와 본문을 가짐

  • 헤더는 요청 또는 응답에 대한 정보를 가짐
  • 본문은 주고받는 실제 데이터
  • 쿠키는 부가적인 정보이므로 헤더에 저장

< 세션 사용하기 >

쿠키의 정보는 노출되고 수정되는 위험이 있음

  • 중요한 정보는 서버에서 관리되고 클라이언트에는 세션 키만 제공
  • 서버에 세션 객체(session)생성 후, uniqueInt(키)를 만들어 속성명으로 사용
  • 속성 값에 정보 저장하고 uniqueInt를 클라이언트에 보냄

https

웹 서버에 SSL 암호화를 추가하는 모듈

  • 오고 가는 데이터를 암호화해서 중간에 다른 사람이 요청을 가로채더라도 내용을 확인할 수 없음
  • 요즘에는 https 적용이 필수(개인 정보가 있는 곳은 특히)
//server1-3.js

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

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번 포트에서 서버 대기 중입니다!');
  });

 

인증기관에서 인증서를 받아야 한다.

https 는 port를 443으로 생략이 가능하다.

http2

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

  • 요청 및 응답 방식이 기존 http/1.1 보다 개선됨
  • 웹의 속도도 개선됨
//server1-4.js
const http2 = require('http2');
const fs = require('fs');

http2.createSecureServer({
  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번 포트에서 서버 대기 중입니다!');
  });

 

cluster

실무에서는 http2 적용하면서 cluster 도 같이 적용하는 것이 좋다.

기본적으로 싱글 스레드인 노드가 CPU 코어를 모두 사용할 수 있게 해주는 모듈

  • 포트를 공유하는 노드 프로세스를 여러 개 둘 수 있음
  • 요청이 많이 들어왔을 때 병렬로 실행된 서버의 개수만큼 요청이 분산됨
  • 서버에 무리가 덜 감
  • 코어가 8개인 서버가 있을 때 : 보통은 코어 하나만 활용
  • cluster로 코어 하나당 노드 프로세스 하나를 배정 가능
  • 성능이 8배가 되는 것은 아니지만 개선됨
  • 단점 : 컴퓨터 자원(메모리, 세션 등) 공유 못함
  • Redis 등 별도 서버로 해결
//cluster.js
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
  console.log(`마스터 프로세스 아이디: ${process.pid}`);
  // CPU 개수만큼 워커를 생산
  for (let i = 0; i < numCPUs; i += 1) {
    cluster.fork();
  }
  // 워커가 종료되었을 때
  cluster.on('exit', (worker, code, signal) => {
    console.log(`${worker.process.pid}번 워커가 종료되었습니다.`);
    console.log('code', code, 'signal', signal);
//    cluster.fork();  주석 처리 하고 코어개수만큼 접속하면 코어개수 넘어갔을 때 서버가 연결이 안됨
// 주석 풀면 코어개수 이상만큼 접속해도 계속 프로세스 띄우기 때문에 서버 연결이 가능함
  });
} else {
  // 워커들이 포트에서 대기
  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(() => { // 워커 존재를 확인하기 위해 1초마다 강제 종료
      process.exit(1);
    }, 1000);
  }).listen(8086);

  console.log(`${process.pid}번 워커 실행`);
}

//실행결과
D:\code\node\4.5>node cluster.js
마스터 프로세스 아이디: 19868
19928 워커 실행
19960 워커 실행
19988 워커 실행
19968 워커 실행
20020 워커 실행
20036 워커 실행
20068 워커 실행
20168 워커 실행
20128 워커 실행
20220 워커 실행
20088 워커 실행
20292 워커 실행
// 코어개수만큼 접속하면 코어개수 넘어갔을 때 서버가 연결이 안됨
20088 워커가 종료되었습니다.
code 1 signal null
20292 워커가 종료되었습니다.
code 1 signal null
20220 워커가 종료되었습니다.
code 1 signal null
20168 워커가 종료되었습니다.
code 1 signal null
20128 워커가 종료되었습니다.
code 1 signal null
20068 워커가 종료되었습니다.
code 1 signal null
20036 워커가 종료되었습니다.
code 1 signal null
20020 워커가 종료되었습니다.
code 1 signal null
19928 워커가 종료되었습니다.
code 1 signal null
19968 워커가 종료되었습니다.
code 1 signal null
19960 워커가 종료되었습니다.
code 1 signal null
19988 워커가 종료되었습니다.
code 1 signal null

 

'Javascript > Node' 카테고리의 다른 글

노드 교과서 섹션 1  (0) 2023.09.06
노드 교과서 섹션 2  (0) 2023.09.05
노드 교과서 섹션 4  (0) 2023.08.31
노드 교과서 섹션 5  (0) 2023.08.29
노드 교과서 섹션 6  (0) 2023.08.27

npm이란

Node Package Manger

  • 노드의 패키지 매니저
  • 다른 사람들이 만든 소스 코드들을 모아둔 저장소
  • 남의 코드를 사용하여 프로그래밍 기능
  • 이미 있는 기능을 다시 구현할 필요가 없어 효율적
  • 오픈 소스 생태계를 구성중
  • 패키지 : npm에 업로드된 노드 모듈
  • 모듈이 다른 모듈을 사용할 수 있듯 패키지도 다른 패키지를 사용할 수 있음
  • 의존 관계라고 부름

package.json

현재 프로젝트에 대한 정보와 사용 중인 패키지에 대한 정보를 담은 파일

  • 같은 패키지라도 버전별로 기능이 다를 수 있으므로 버전을 기록해두어야 함
  • 동일한 버전을 설치하지 않으면 문제가 생길 수 있음
  • 노드 프로젝트 시작 전 package.json 부터 만들고 시작함(npm init)
D:\code\node\publish>npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (publish) npmtest
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author: hd
license: (ISC) MIT
About to write to D:\code\node\publish\package.json:

{
  "name": "npmtest",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "hd",
  "license": "MIT",
  "description": ""
}


Is this OK? (yes) yes
// npm init 완료 하면 package.json 파일이 생성됨

// package.json
{
  "name": "npmtest",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "hd",
  "license": "MIT",
  "description": ""
}

 

>> npm run [스크립트명]으로 스크립트 생성

 

D:\code\node\publish>npm run test

> npmtest@1.0.0 test
> echo "Error: no test specified" && exit 1   //에러를 발생시키면서 프로세스를 종료

"Error: no test specified"

 

// 아래와 같이 설치를 하면 package.json 에 dependencies 에 버전이 표시된다.
// node_modules 디렉토리가 생성되면서 스크립트들이 생긴다.
D:\code\node\publish>npm i express

added 64 packages, and audited 65 packages in 8s

12 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

//package.json
{
  "name": "npmtest",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "hd",
  "license": "MIT",
  "description": "",
  "dependencies": {
    "express": "^4.19.2"
  }
}

 

// 아래와 같이 -D 옵션으로 설치를 하면 package.json 에 devDependencies에 버전이 표시된다.
// devDependencies는 개발할 때만 쓰이는 패키지들이다.
D:\code\node\publish>npm i -D nodemon

added 29 packages, and audited 94 packages in 6s

16 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

//package.json
  "devDependencies": {
    "nodemon": "^3.1.4"
  }

node_modules 크기가 크기 때문에 지워서 배포를 한다.(package.json 만 가지고 있다가 설치를 한다.)

npm i -g rimraf (-g는 글로벌 설치, 글로벌 설치는 dependencies 에 기록되지 않는다.)

npm i rimraf -D

npx 를 사용하면 글로벌 설치한 것처럼 사용이 가능하다.

글로벌 설치는 최대한 기피하는 것이 좋다.

SemVer 버저닝

노드 패키지의 버전은 SermVer(유의적 버저닝)방식을 따름

  • Major(주 버전), Minor(부 버전), Patch(수 버전)
  • 노드에서는 배포를 할 때 항상 버전을 올려야 함
  • Major는 하위 버전과 호환되지 않은 수정 사항이 생겼을 때 올림
  • Minor는 하위 버전과 호환되는 수정 사항이 생겼을 때 올림
  • Patch는 기능에 버그를 해결했을 때 올림

예) 1.0.7 ←- 1 : Major

        ←- 0 : Minor

              ←- 7 : Patch

버전 앞에 기호를 붙여 의미를 더함

dependencies에서 아래와 같이 고정된다는 뜻이다.

^1.0.7 : Major까지 고정

~1.0.7 : Minor까지 고정

1.0.7 : Patch까지 고정

  • @latest는 최신을 의미
  • @next로 가장 최신 배포판 사용 가능(불안정함)
  • 알파/베타/RC 버전이 존재할 수 있음

npm i express@latest ← 최신 버전 설치

npm i express@3 ← Major 3인 버전 설치

npm i express@3.5.1 ← 3.5.1 정확한 버전 설치

npm i express@next ← 가장 최신 배포판

 

npm outdated : 어떤 패키지에 기능 변화가 생겼는지 알 수 있음

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

npm search 패키지명 : npm 패키지를 검색할 수 있음

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

D:\code\node\publish>npm search express

express
Fast, unopinionated, minimalist web framework
Version 4.19.2 published 2024-03-25 by wesleytodd
Maintainers: dougwilson linusu sheplu blakeembrey ulisesgascon wesleytodd mikeal
Keywords: express framework sinatra web http rest restful router app api
https://npm.im/express

D:\code\node\publish>npm info express

express@4.19.2 | MIT | deps: 31 | versions: 276
Fast, unopinionated, minimalist web framework
http://expressjs.com/


npm publish : 자신이 만든 패키지를 배포

npm unpublish : 자신이 만든 패키지를 배포 중단(배포 후 24시간 내에만 가능)

https://docs.npmjs.com/cli/v9/commands ←명령어 바뀌는 경우 있으니 사이트 참조

 

'Javascript > Node' 카테고리의 다른 글

노드 교과서 섹션 2  (0) 2023.09.05
노드 교과서 섹션 3  (0) 2023.09.01
노드 교과서 섹션 5  (0) 2023.08.29
노드 교과서 섹션 6  (0) 2023.08.27
노드 교과서 섹션 8  (0) 2023.08.23

express 와 nodemon 를 npm i 로 설치하고 아래와 같이 실행하면 익스프레스 서버가 실행된다.

 

// app.js

const express = require('express');

const app = express();

app.set('port', process.env.PORT || 3000);
app.get('/', (req, res)=>{
    res.send('hello express!');
});

app.listen(app.get('port'), ()=> {
    console.log('익스프레스 서버 실행');
});

 


// nodemon 으로 실행하면  파일에 변화가 있을 때마다 재기동을 해준다.
D:\code\node\6.1>nodemon app
[nodemon] 3.1.4
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,cjs,json
[nodemon] starting `node app.js`
익스프레스 서버 실행
[nodemon] restarting due to changes...
[nodemon] starting `node app.js`
익스프레스 서버 실행

// package.json 에서 scripts에 아래와 같이 되어 있으면 npm start 로도 실행이 가능하다.
  "scripts": {
    "start": "nodemon app"
  },

//실행 결과
D:\code\node\6.1>npm start

> express_test@1.0.0 start
> nodemon app

[nodemon] 3.1.4
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,cjs,json
[nodemon] starting `node app.js`
익스프레스 서버 실행

 

express 에서 html 파일 불러오기

 

//app.js
const express = require('express');
const path = require('path');
const app = express();

app.set('port', process.env.PORT || 3000);
app.get('/', (req, res)=>{
    res.sendFile(path.join(__dirname,'index.html'));
});

app.listen(app.get('port'), ()=> {
    console.log('익스프레스 서버 실행');
});

 

nodemon은 html 파일을 감시하지 않는다.

 

와일드카드는 보통 다른 라우터들보다 아래에 위치해야 정상적으로 작동된다.

미들웨어는 next()를 해주어야 그 다음 미들웨어가 실행된다.

미들웨어는 여러 개를 같이 사용도 가능하다.

에러 미들웨어는 반드시 next까지 4개가 다 들어있어야 한다.

서버가 클라이언트에게 status code를 마음대로 보낼 수가 있다.(에러가 발생해도)

 

res.json 은 응답을 보낼 뿐이지 return을 하는 것이 아니다.

 

// morgan cookie-parser express-session 설치
D:\code\node\6.1>npm i morgan cookie-parser express-session

added 11 packages, and audited 105 packages in 1s

16 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

 

morgan을 설치하고 실행한 후에 접속하면 비슷한 것 같지만 한 줄이 추가된다.

클라이언트에서 어떤 요청이 왔는지 서버에 기록된다.

얼마나 속도가 걸렸는지, 용량은 얼만큼인지(아래는 173 byte) , status code 도 같이 기록된다.

 

// 클라이언트에서 어떤 요청 왔는지 기록됨.
D:\code\node\6.1>npm start

> express_test@1.0.0 start
> nodemon app

[nodemon] 3.1.4
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,cjs,json
[nodemon] starting `node app.js`
익스프레스 서버 실행
모든 요청에 실행하고 싶어요.
GET / 200 10.919 ms - 173
모든 요청에 실행하고 싶어요.
GET /favicon.ico 404 1.772 ms - 14

 

// 개발시에는 dev
app.use(morgan('dev'));
//  실무에서는 combined 사용, 조금 더 자세해진다. ip , 시간, 요청 , 브라우저 등등 알 수 있다.
app.use(morgan('combined'));

 

app.use(cookieParser('zerochopassword'));
app.get('/', (req, res)=>{
    req.cookies // {mycookie : 'test}
    req.signedCookies; // 서명된 쿠키
    // 쿠키 생성 (name deprecated)
    res.cookie('name', encodeURIComponent(name), {
        expires: new Date(),
        httpOnly : true,
        path : '/',
    })
    // 쿠키 삭제 (name deprecated)
    res.clearCookie('name', encodeURIComponent(name),{
        httpOnly: true,
        path : '/',
    })
});

 

// bodyparser 가 express 안에 있다.
app.use(express.json()); // 클라이언트에서 json 데이터를 보냈을 때 파싱
app.use(express.urlencoded({extended: true})); //클라이언트에서 폼 데이터 보냈을 때 파싱
//extend가 true 면 qs, false 면 querystring (qs를 추천)

 

// 정적파일 처리
// app.use('요청경로',express.static('실제 경로'));
app.use('/',express.static(__dirname, 'public-3030')); // public은 유명하니깐 다른 것을 붙이는 것이 보안에 좋다.
//localhost:3000/zerocho.html         d:\code\node\6.1\public-3030\zerocho.html
//localhost:3000/hello.css         d:\code\node\6.1\public-3030\hello.css

 

'Javascript > Node' 카테고리의 다른 글

노드 교과서 섹션 3  (0) 2023.09.01
노드 교과서 섹션 4  (0) 2023.08.31
노드 교과서 섹션 6  (0) 2023.08.27
노드 교과서 섹션 8  (0) 2023.08.23
노드 교과서 섹션 9  (0) 2023.08.22

MySQL과 같은 SQL 데이터베이스와는 다른 유형의 데이터

  • NoSQL의 대표주자인 mongoDB(몽고디비)사용
SQL(MySQL) NoSQL(몽고디비)
규칙에 맞는 데이터 입력 테이블 간 JOIN 지원 안정성, 일관성 용어(테이블, 로우, 칼럼) 자유로운 데이터 입력 컬렉션 간 JOIN 미지원 확장성, 가용성 용어(컬렉션, 다큐먼트, 필드)

 

  • JOIN : 관계가 있는 테이블끼리 데이터를 합치는 기능(몽고디비 aggregate 흉내 가능)
  • 빅데이터, 메시징, 세션 관리 등(비정형 데이터)에는 몽고디비 사용하면 좋음
//몽고디비 실행 (아래 경로에서 shift 누르고 오른쪽 마우스 눌러서 powershell 실행
PS C:\Program Files\MongoDB\Server\7.0\bin> ./mongod --ipv6
{"t":{"$date":"2024-08-14T22:56:08.454+09:00"},"s":"I",  "c":"NETWORK",  "id":4915701, "ctx":"thread1","msg":"Initialized wire specification","attr":{"spec":{"incomingExternalClient":{"minWireVersion":0,"maxWireVersion":21},"incomingInternalClient":{"minWireVersion":0,"maxWireVersion":21},"outgoing":{"minWireVersion":6,"maxWireVersion":21},"isInternalClient":true}}}

 

//몽고쉘 실행
Please enter a MongoDB connection string (Default: mongodb://localhost/):

Current Mongosh Log ID: 66bcb84be9c5ca3e0a228fb4
Connecting to:          mongodb://127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+2.2.15
Using MongoDB:          7.0.12
Using Mongosh:          2.2.15

For mongosh info see: https://docs.mongodb.com/mongodb-shell/


To help improve our products, anonymous usage data is collected and sent to MongoDB periodically (https://www.mongodb.com/legal/privacy-policy).
You can opt-out by running the disableTelemetry() command.

------
   The server generated these startup warnings when booting
   2024-08-14T22:56:09.973+09:00: Access control is not enabled for the database. Read and write access to data and configuration is unrestricted
   2024-08-14T22:56:09.973+09:00: This server is bound to localhost. Remote systems will be unable to connect to this server. Start the server with --bind_ip <address> to specify which IP addresses it should serve responses from, or with --bind_ip_all to bind to all interfaces. If this behavior is desired, start the server with --bind_ip 127.0.0.1 to disable this warning
------

test>
// admin 으로 변경
test> use admin
switched to db admin
// root 계정 생성
admin> db.createUser({user:'root', pwd:'manager', roles: ['root']})
{ ok: 1 }

 

mongod 있는 화면 끄고 다시 재실행

// --auth 붙여서 다시 실행
PS C:\Program Files\MongoDB\Server\7.0\bin> ./mongod --ipv6 --auth
{"t":{"$date":"2024-08-14T23:07:03.228+09:00"},"s":"I",  "c":"NETWORK",  "id":4915701, "ctx":"thread1","msg":"Initialized wire specification","attr":{"spec":{"incomingExternalClient":{"minWireVersion":0,"maxWireVersion":21},"incomingInternalClient":{"minWireVersion":0,"maxWireVersion":21},"outgoing":{"minWireVersion":6,"maxWireVersion":21},"isInternalClient":true}}}

 

//몽고쉘 경로에서 shift 누르고 오른쪽 마우스로 파워셀 열고 아래와 같이  root 계정으로 접속
PS C:\Users\john\Downloads\mongosh-2.2.15-win32-x64\mongosh-2.2.15-win32-x64\bin> ./mongosh admin -u root -p manager
Current Mongosh Log ID: 66bcbaa044900f7385228fb4
Connecting to:          mongodb://<credentials>@127.0.0.1:27017/admin?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+2.2.15
Using MongoDB:          7.0.12
Using Mongosh:          2.2.15

For mongosh info see: https://docs.mongodb.com/mongodb-shell/

------
   The server generated these startup warnings when booting
   2024-08-14T23:07:03.627+09:00: This server is bound to localhost. Remote systems will be unable to connect to this server. Start the server with --bind_ip <address> to specify which IP addresses it should serve responses from, or with --bind_ip_all to bind to all interfaces. If this behavior is desired, start the server with --bind_ip 127.0.0.1 to disable this warning
------

admin>

 

컬렉션 생성하기

따로 생성할 필요 없음

  • 다큐먼트를 넣는 순간 컬렉션도 자동 생성됨 ( mysql과 비교하며 컬렉션 : 테이블, 다큐먼트 : 데이터 로우)
  • 직접 생성하는 명령어도 있음
// 컬렉션 직접 생성하는 명령어 ( db를 nodejs로  잘 선택해서 생성하기)
admin> use nodejs;
switched to db nodejs
nodejs> show dbs;
admin   132.00 KiB
config  108.00 KiB
local    72.00 KiB
nodejs> db
nodejs
nodejs> db.createCollection('users')
{ ok: 1 }
nodejs> db.createCollection('comments')
{ ok: 1 }
nodejs> show collections
comments
users
nodejs>

 

insert 명령어

 

// insert 한개는 insertOne, insert 여러개는 insertMany
// 몽고디비는 오타 체크를 안하니 주의하기
nodejs> db.users.insertOne({ name : 'zero', age: 24, married: false, comment: '안녕하세요. 간단히 몽고디비 사용 방법에  대해 알아봅시다.', createdAt: new Date() });
{
  acknowledged: true,
  insertedId: ObjectId('66bcbdc744900f7385228fb5')
}
nodejs> db.users.insertOne({ name : 'nero', age : 32, married: true, comment: '안녕하세요. zero 친구 nero입니다.', createdAt: new Date() });
{
  acknowledged: true,
  insertedId: ObjectId('66bcbe7144900f7385228fb6')
}

// 조회하기
nodejs> db.users.find({name:'zero'})
[
  {
    _id: ObjectId('66bcbdc744900f7385228fb5'),
    name: 'zero',
    age: 24,
    married: false,
    comment: '안녕하세요. 간단히 몽고디비 사용 방법에 대해 알아봅시다.',
    createdAt: ISODate('2024-08-14T14:23:03.633Z')
  }
]


// users에 해당하는 ObjectId를 아래와 같이  복사해서 insert 하는 명령어
nodejs> db.comments.insertOne({ commenter: ObjectId('66bcbdc744900f7385228fb5'), comment: '안녕하세요. zero의 댓글입니다.', createdAt: new Date() });
{
  acknowledged: true,
  insertedId: ObjectId('66bcbf5d44900f7385228fb7')
}
nodejs>

 

조회 명령어

// 조회하기 (find는 전체 조회, findOne으로 하나만 조회)
nodejs> db.users.find({});
[
  {
    _id: ObjectId('66bcbdc744900f7385228fb5'),
    name: 'zero',
    age: 24,
    married: false,
    comment: '안녕하세요. 간단히 몽고디비 사용 방법에 대해 알아봅시다.',
    createdAt: ISODate('2024-08-14T14:23:03.633Z')
  },
  {
    _id: ObjectId('66bcbe7144900f7385228fb6'),
    name: 'nero',
    age: 32,
    married: true,
    comment: '안녕하세요. zero 친구 nero입니다.',
    createdAt: ISODate('2024-08-14T14:25:53.749Z')
  }
]
nodejs> db.comments.find({});
[
  {
    _id: ObjectId('66bcbf5d44900f7385228fb7'),
    commenter: ObjectId('66bcbdc744900f7385228fb5'),
    comment: '안녕하세요. zero의 댓글입니다.',
    createdAt: ISODate('2024-08-14T14:29:49.856Z')
  }
]

 

두 번째 인수로 조회할 필드를 선택할 수 있음(1은 추가, 0은 제외)

nodejs> db.users.find({}, {_id : 0, name :1 , married:1 });
[ { name: 'zero', married: false }, { name: 'nero', married: true } ]

 

첫 번째 인수로 조회 조건 입력 가능

  • $gt나 $or같은 조건 연산자 사용
nodejs> db.users.find({ age: {$gt: 30}, married: true }, {_id : 0, name: 1, age: 1 });
[ { name: 'nero', age: 32 } ]
nodejs> db.users.find({ $or: [{ age : {$gt: 30}}, {married : false }]}, {_id : 0, name:1, age: 1});
[ { name: 'zero', age: 24 }, { name: 'nero', age: 32 } ]

 

update 명령어

 

nodejs> db.users.updateOne({name :'nero'}, {$set: { comment : '안녕하세요. 이 필드를 바꿔보겠습니다.'}});
{
  acknowledged: true, //정상반영
  insertedId: null,
  matchedCount: 1,  //하나를 찾아서
  modifiedCount: 1,  // 하나를 변경햇다.
  upsertedCount: 0
}

 

delete 명령어

//delete 명령어
nodejs> db.users.deleteOne({name: 'nero'});
{ acknowledged: true, deletedCount: 1 }

// delete 된 것 확인
nodejs> db.users.find({});
[
  {
    _id: ObjectId('66bcbdc744900f7385228fb5'),
    name: 'zero',
    age: 24,
    married: false,
    comment: '안녕하세요. 간단히 몽고디비 사용 방법에 대해 알아봅시다.',
    createdAt: ISODate('2024-08-14T14:23:03.633Z')
  }
]

 

 

'Javascript > Node' 카테고리의 다른 글

노드 교과서 섹션 4  (0) 2023.08.31
노드 교과서 섹션 5  (0) 2023.08.29
노드 교과서 섹션 8  (0) 2023.08.23
노드 교과서 섹션 9  (0) 2023.08.22
노드 교과서 섹션 10  (0) 2023.08.19

lecture 라는 디렉토리를 생성하고 npm init 으로 시작

 

D:\code\node\lecture>npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (lecture) nodebird
version: (1.0.0) 0.0.1
description: 익스프레스를 만드는 SNS 서비스
entry point: (index.js) app.js
test command:
git repository:
keywords:
author:
license: (ISC) MIT
About to write to D:\code\node\lecture\package.json:    

{
  "name": "nodebird",
  "version": "0.0.1",
  "description": "익스프레스를 만드는 SNS 서비스",      
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "MIT"
}


Is this OK? (yes) yes

 

// 필요한 모듈 설치
D:\code\node\lecture>npm i sequelize mysql2 sequelize-cli

D:\code\node\lecture>npx sequelize init

D:\code\node\lecture>npm i express cookie-parser express-session morgan multer dotenv nunjucks

D:\code\node\lecture>npm i -D nodemon

 

app.js 가 메인 파일이다.

 

router 에서 필요한 것들은 res.locals를 사용한다.

 

// routes/page.js

const express = require('express');
const router = express.Router();
const {renderJoin, renderMain, renderProfile} = require('../controllers/page');

router.use((req, res, next)=>{
    res.locals.user = null;
    res.locals.follerCount = 0;
    res.locals.followingCount = 0;
    res.locals.followingIdList = [];
    next();
})

router.get('/profile', renderProfile);
router.get('/join', renderJoin);
router.get('/', renderMain);


module.exports = router;

 

미들웨어 마지막에 위치하는 것들을 컨트롤러라고 부른다.

// controllers/page.js

exports.renderProfile = (req, res, next) =>{
    // 서비스를 호출
    res.redner('profile', {title : '내 정보 - NoreBird'});
};

exports.renderJoin = (req, res, next )=>{
    res.render('join', {title : '회원 가입 - NodeBird'});
};

exports.renderMain = (req, res, next )=>{
    res.render('main', {
        title : 'Nodebird',
        twits: [],
    });
};

// 라우터 -> 컨트롤러 -> 서비스(요청, 응답 모른다.)

 

아래와 같이 app.js 를 작성한다.

 

// app.js 파일

const express = require('express');
const cookieParser = require('cookie-parser');
const morgan = require('morgan');
const path = require('path');
const session = require('express-session');
const nunjucks = require('nunjucks');
const dotenv = require('dotenv');

dotenv.config(); // process.env
const pageRouter = require('./routes/page')

const app = express();
app.set('port', process.env.PORT || 8001);
app.set('view engine','html');
nunjucks.configure('views',{
    express: app,
    watch: true,
});

app.use(morgan('dev'));
app.use(express.static(path.join(__dirname,'public')));
app.use(express.json());
app.use(express.urlencoded({extended : false}));
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(session({
    resave : false,
    saveUninitialized :false,
    secret: process.env.COOKIE_SECRET,
    cookie : {
        httpOnly : true,
        secret : false,
    }
}));

app.use('/', pageRouter);
app.use((req,res,next)=>{ // 404 NOT FOUND
    const error = new Error(`${req.method} ${req.url} 라우터가 없습니다.`);
    error.status = 404;
    next(error);
});

app.use((err, req, res, next)=>{
    res.locals.message = err.message;
    res.locals.error = process.env.NODE_ENV !== 'production' ? err : {}; // 에러 로그를 서비스한테 넘김
    res.status(err.status || 500);
    res.render('error');
});

app.listen(app.get('port'),()=>{
    console.log(app.get('port'),'번 포트에서 대기 중');
});

 

에러 발생! 다행히 검색해서 빨리 해결함 (오타..)

// 아래와 같이 에러 발생, 알려주는 위치로 갔는데 내가 작성한 파일이 아니므로
// 구글 검색 후 app.js 에서 engine 설정하는 부분에서 오타 발견하여 해결함.

[nodemon] starting `node app.js`
8001 포트에서 대기
GET / 500 9.394 ms - 1190
Error: No default engine was specified and no extension was provided.
    at new NunjucksView (D:\code\node\lecture\node_modules\nunjucks\src\express-app.js:11:13)
   

// engine 오타 였음..    
app.set('view engine','html');

 

에러 해결하고 다행히 아래와 같이 프로젝트 구성 후 실행 성공

sequelize 설정과 models 디렉토리 , config\config.json 에서 DB 설정을 완료한 후에 아래처럼 DB를 생성해준다.

 

 

D:\code\node\lecture>npx sequelize db:create

Sequelize CLI [Node: 20.16.0, CLI: 6.6.2, ORM: 6.37.3]

Loaded configuration file "config\config.json".
Using environment "development".
Database nodejs created.

 

그 이후에 npm start 했는데 아래와 같이 에러 발생

D:\code\node\lecture>npm start

> nodebird@0.0.1 start
> nodemon app.js

[nodemon] 3.1.4
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,cjs,json
[nodemon] starting `node app.js`
hashtag.js Hashtag
post.js Post
D:\code\node\lecture\models\index.js:24
    model.initiate(sequelize);
          ^

TypeError: model.initiate is not a function

 

해당 파일인 index.js 파일 찾았는데도 오타 발견 못하였고, models 안에 있는 다른 .js 문제라고 생각되어 제로초님 깃허브와 비교한 결과 models 안에 있는 다른.js 에서 오타 찾아서 다시 수행하니 에러 해결되고 테이블 생성됨.

 

Node.js v20.16.0
[nodemon] app crashed - waiting for file changes before starting...
[nodemon] restarting due to changes...
[nodemon] starting `node app.js`
hashtag.js Hashtag
post.js Post
user.js User
8001 포트에서 대기
Executing (default): SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = 'hashtags' AND TABLE_SCHEMA = 'nodejs'
Executing (default): CREATE TABLE IF NOT EXISTS `hashtags` (`id` INTEGER NOT NULL auto_increment , `title` VARCHAR(15) NOT NULL UNIQUE, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, PRIMARY
KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;
Executing (default): SHOW INDEX FROM `hashtags` FROM `nodejs`
Executing (default): SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = 'users' AND TABLE_SCHEMA = 'nodejs'
Executing (default): CREATE TABLE IF NOT EXISTS `users` (`id` INTEGER NOT NULL auto_increment , `email` VARCHAR(40) UNIQUE, `nick` VARCHAR(15) NOT NULL, `password` VARCHAR(100), `provider` ENUM('local', 'kakao') NOT NULL DEFAULT 'local', `snsId` VARCHAR(30), `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, `deletedAt` DATETIME, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_general_ci;
Executing (default): SHOW INDEX FROM `users` FROM `nodejs`
Executing (default): SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = 'posts' AND TABLE_SCHEMA = 'nodejs'
Executing (default): CREATE TABLE IF NOT EXISTS `posts` (`id` INTEGER NOT NULL auto_increment , `content` VARCHAR(140) NOT NULL, `img` VARCHAR(200), `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, `UserId` INTEGER, PRIMARY KEY (`id`), FOREIGN KEY (`UserId`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;
Executing (default): SHOW INDEX FROM `posts` FROM `nodejs`
Executing (default): SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = 'PostHashtag' AND TABLE_SCHEMA = 'nodejs'
Executing (default): CREATE TABLE IF NOT EXISTS `PostHashtag` (`createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, `HashtagId` INTEGER , `PostId` INTEGER , PRIMARY KEY (`HashtagId`, `PostId`), FOREIGN KEY (`HashtagId`) REFERENCES `hashtags` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (`PostId`) REFERENCES `posts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;
Executing (default): SHOW INDEX FROM `PostHashtag` FROM `nodejs`
Executing (default): SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = 'Follow' AND TABLE_SCHEMA = 'nodejs'
Executing (default): CREATE TABLE IF NOT EXISTS `Follow` (`createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, `followingId` INTEGER , `followerId` INTEGER , PRIMARY KEY (`followingId`, `followerId`), FOREIGN KEY (`followingId`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN
KEY (`followerId`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_general_ci;
Executing (default): SHOW INDEX FROM `Follow` FROM `nodejs`
데이터베이스 연결 성공

 

위와 같이 테이블 생성된 것 확인.

 

passport 설정하고 회원 가입하는 코드까지만 작성하고 서버 시작해서 잘 가입되는지 확인

 

D:\code\node\lecture>npm start

> nodebird@0.0.1 start
> nodemon app.js

[nodemon] 3.1.4
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,cjs,json
[nodemon] starting `node app.js`
hashtag.js Hashtag
post.js Post
user.js User
8001 포트에서 대기
Executing (default): SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = 'hashtags' AND TABLE_SCHEMA = 'nodejs'
Executing (default): SHOW INDEX FROM `hashtags` FROM `nodejs`
Executing (default): SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = 'users' AND TABLE_SCHEMA = 'nodejs'
Executing (default): SHOW INDEX FROM `users` FROM `nodejs`
Executing (default): SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = 'posts' AND TABLE_SCHEMA = 'nodejs'
Executing (default): SHOW INDEX FROM `posts` FROM `nodejs`
Executing (default): SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = 'PostHashtag' AND TABLE_SCHEMA = 'nodejs'
Executing (default): SHOW INDEX FROM `PostHashtag` FROM `nodejs`
Executing (default): SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = 'Follow' AND TABLE_SCHEMA = 'nodejs'
Executing (default): SHOW INDEX FROM `Follow` FROM `nodejs`
데이터베이스 연결 성공

 

아래와 같이 회원 가입되면 insert into 구문이 보이고 302 코드를 반환한 것을 보면 성공적으로 가입된 것을 확인할 수 있다.

 


GET /main.css 200 3.224 ms - 2715
GET /join 200 2.651 ms - 2510
GET /main.css 304 0.854 ms - -
Executing (default): SELECT `id`, `email`, `nick`, `password`, `provider`, `snsId`, `createdAt`, `updatedAt`, `deletedAt` FROM `users` AS `User` WHERE (`User`.`deletedAt` IS NULL AND `User`.`email` = 'a@b.com');
Executing (default): INSERT INTO `users` (`id`,`email`,`nick`,`password`,`provider`,`createdAt`,`updatedAt`) VALUES (DEFAULT,?,?,?,?,?,?);
POST /auth/join 302 555.560 ms - 46
Executing (default): SELECT `Post`.`id`, `Post`.`content`, `Post`.`img`, `Post`.`createdAt`, `Post`.`updatedAt`, `Post`.`UserId`, `User`.`id` AS `User.id`, `User`.`nick` AS `User.nick` FROM `posts` AS `Post` LEFT OUTER JOIN `users` AS `User` ON `Post`.`UserId` = `User`.`id` AND (`User`.`deletedAt` IS NULL) ORDER BY `Post`.`createdAt` DESC;
GET / 200 20.424 ms - 3247

 

실제 DB에서도 정상적으로 잘 입력된 것 확인 가능.

 

 

로그인 기능까지 코드 작성한 뒤에 로그인 했을 때 성공한 화면

실제로 DB에서도 정상적으로 입력된 것도 확인

 

카카오톡 로그인 코드 작성한 뒤에 로그인 했을 때 성공한 화면

하지만 카카오톡 로그인이 강의 시점과 달라져서 아래와 같이 이메일은 권한이 없어서 못 받아오는 것으로 보인다. 비즈앱으로 등록을 해야만 권한이 허용되는 것 같다.

 

 

그래도 이름, snsID는 받아와서 로그인은 되었지만 아래와 같이 DB에는 이름만 받아오는 것을 확인할 수 있었다.

 

해시태그를 게시판 내용 안에서 추출하는 정규표현식 node 로 직접 수행해보면 아래와 같다

D:\code\node\lecture>node
Welcome to Node.js v20.16.0.
Type ".help" for more information.
> const str = '노드 교과서 너무 재밌어요. #노드교과서 #익스프레스 짱짱'
undefined
> str.match(/#[^\s#]*/g);
[ '#노드교과서', '#익스프레스' ]

 

해시태그 포함해서 게시판 글쓰기 코드 작성 후에 아래와 같이 이미지 올린 결과는 아래와 같다.

게시판 업로드까지 완료한 화면은 아래와 같다.

아래와 같이 DB에도 잘 들어간 것을 확인

'Javascript > Node' 카테고리의 다른 글

노드 교과서 섹션 5  (0) 2023.08.29
노드 교과서 섹션 6  (0) 2023.08.27
노드 교과서 섹션 9  (0) 2023.08.22
노드 교과서 섹션 10  (0) 2023.08.19
노드 교과서 섹션 11  (0) 2023.08.18

기존에 만들었던 노드버드 서비스가 기동된 상태에서 localhost:8002 로 별도의 api 서버를 만든다. routes 나 controllers 에서 auth 를 제외한 파일들을 지워주고 index.js 를 만들어 준다. 그리고 indexRouter를 생성해준다.

 

//app.js 파일

const express = require('express');
const cookieParser = require('cookie-parser');
const morgan = require('morgan');
const path = require('path');
const session = require('express-session');
const nunjucks = require('nunjucks');
const dotenv = require('dotenv');
const passport = require('passport');

dotenv.config();
const authRouter = require('./routes/auth');
const indexRouter = require('./routes');
const { sequelize } = require('./models');
const passportConfig = require('./passport');

const app = express();
passportConfig(); // 패스포트 설정
app.set('port', process.env.PORT || 8002);
app.set('view engine', 'html');
nunjucks.configure('views', {
  express: app,
  watch: true,
});
sequelize.sync({ force: false })
  .then(() => {
    console.log('데이터베이스 연결 성공');
  })
  .catch((err) => {
    console.error(err);
  });

app.use(morgan('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(session({
  resave: false,
  saveUninitialized: false,
  secret: process.env.COOKIE_SECRET,
  cookie: {
    httpOnly: true,
    secure: false,
  },
}));
app.use(passport.initialize());
app.use(passport.session());


app.use('/auth', authRouter);
app.use('/', indexRouter);

app.use((req, res, next) => {
  const error =  new Error(`${req.method} ${req.url} 라우터가 없습니다.`);
  error.status = 404;
  next(error);
});

app.use((err, req, res, next) => {
  res.locals.message = err.message;
  res.locals.error = process.env.NODE_ENV !== 'production' ? err : {};
  res.status(err.status || 500);
  res.render('error');
});

app.listen(app.get('port'), () => {
  console.log(app.get('port'), '번 포트에서 대기중');
});

 

Domain 테이블을 만들어준다.

 

// models/domain.js 파일

const Sequelize = require("sequelize");

class Domain extends Sequelize.Model {
  static initiate(sequelize) {
    Domain.init(
      {
        host: {
          type: Sequelize.STRING(80),
          allowNull: false,
        },
        type: {
          type: Sequelize.ENUM("free", "premium"),
          allowNull: false,
        },
        clientSecret: {
          type: Sequelize.UUID,
          allowNull: false,
        },
      },
      {
        sequelize,
        timestamps: true,
        paranoid: true,
        modelName: "Domain",
        tableName: "domains",
      }
    );
  }

  static associate(db) {
    db.Domain.belongsTo(db.User);
  }
}

module.exports = Domain;

 

계정은 기존에 8001에서 만들었던 계정을 사용하고 로그인 전 화면과 로그인 후에 화면은 아래와 같다.

 

controllers에서 Domain 만드는 코드까지 작성한 후에 화면은 아래와 같다.

// controllers/index.js 파일

const { User, Domain } = require("../models");
const { v4: uuidv4 } = require("uuid");

exports.renderLogin = async (req, res, next) => {
  try {
    const user = await User.findOne({
      where: { id: req.user?.id || null },
      include: { model: Domain },
    });
    res.render("login", {
      user,
      domains: user?.Domains,
    });
  } catch (err) {
    console.error(err);
    next(err);
  }
};
exports.createDomain = async (req, res, next) => {
  try {
    await Domain.create({
      UserId: req.user.id,
      host: req.body.host,
      type: req.body.type,
      clientSecret: uuidv4(), //랜덤한 uuid를 넣어준다.
    });
    res.redirect("/");
  } catch (err) {
    console.error(err);
    next(err);
  }
};

 

Executing (default): INSERT INTO `domains` (`id`,`host`,`type`,`clientSecret`,`createdAt`,`updatedAt`,`UserId`) VALUES (DEFAULT,?,?,?,?,?,?);

 

토큰이 여러 종류가 있는데 JWT (JasonWebToken) 을 많이 사용한다.

헤더, 페이로드, 시그니처 3종류로 나누어져 있다.

jwt의 단점은 내용물이 들어있기 때문에 용량이 크다.

lecture-call 이라는 클라이언트 역할의 서버와

lecture-api 라는 api를 제공하는 서버의 코드를 작성한다. 작성한 후에 localhost:4000/test 로 접속을 하면 lecture-api 서버에서는 아래와 같이 200 코드가 출력되고 토큰을 클라이언트로 넘겨준다.

 

POST /v1/token 200 30.022 ms - 250
GET /v1/test 200 3.573 ms - 78

 

자기가 작성한 게시글 과 해시태그 검색하는 코드를 작성한 후 결과는 아래와 같습니다.

 

작성한 글 결과 화면

 

해시태그 검색한 결과 화면

 

API 사용량 제한을 위해서는 express-rate-limit 을 설치해줘야 한다.

D:\code\node\lecture-api>npm i express-rate-limit

added 1 package, and audited 300 packages in 3s

41 packages are looking for funding
  run `npm fund` for details
 

 

- v1 버전을 사용했을 때 : call 하는 클라이언트 서버의 request 함수에서 throw error 로 처리하면 아래와 같이 에러코드만 나오고 안내 메세지가 안 나온다.

 


//controller/

const request = async (req, api) => {
    try {
      if (!req.session.jwt) {
        // 세션에 토큰이 없으면
        const tokenResult = await axios.post(`${URL}/token`, {
          clientSecret: process.env.CLIENT_SECRET,
        });
        req.session.jwt = tokenResult.data.token; // 세션에 토큰 저장
      }
      return await axios.get(`${URL}${api}`, {
        headers: { authorization: req.session.jwt },
      }); // API 요청
    } catch (error) {
      if (error.response?.status === 419) {
        // 토큰 만료시 토큰 재발급 받기
        delete req.session.jwt;
        return request(req, api);
      } // 419 외의 다른 에러면
      throw error;
      // return error.response;
    }
  };

 

return error.message 로 변경하면 아래와 같이 안내 메세지가 화면에 출력 된다.

v2로 변경하고 나서는 아래와 같이 정상적으로 호출된다.

 

하지만 1분에 1번만 호출되도록 제한했기 때문에 다시 호출하면 정상 호출 안된다.

 

웹브라우저에서 api를 호출할 때는 아래와 같이 cors 에러가 발생한다. 기존에는 서버에서 직접 api를 호출 했기 때문에 cors 에러가 발생하지 않았다.

 

cors 에러를 해결하기 위해 cors를 설치하고 추가해주면 아래와 같이 해결된다.

D:\code\node\lecture-api>npm i cors

added 1 package, and audited 301 packages in 3s

41 packages are looking for funding
  run `npm fund` for details

 

'Javascript > Node' 카테고리의 다른 글

노드 교과서 섹션 6  (0) 2023.08.27
노드 교과서 섹션 8  (0) 2023.08.23
노드 교과서 섹션 10  (0) 2023.08.19
노드 교과서 섹션 11  (0) 2023.08.18
노드 교과서 섹션 12  (0) 2023.08.17

+ Recent posts