🌿 Node

노드 이해하기

ji-hyun 2022. 2. 13. 00:06

node.js 코딩 패턴

Routes - Controllers - Services

 

node.js 는 코드의 특성별로 소스 파일을 나눈다고 한다.

처음에 잘 구조를 잡아두어야 유지보수할 때 큰 이득을 볼 수 있다....!

 

 

 

 


먼저 개념 정리...

 

 

모듈

모듈이란 관련된 코드들을 하나의 코드 단위로 캡슐화하는 것을 말한다.

다음과 같은 greeting.js 라는 파일이 있다고 하자. 이 파일은 두개의 함수를 포함하고 있다.

 

 

 

 

모듈 추출하기(exporting)

greeting.js 의 코드가 다른 파일에서 사용될 때 그 효용성이 증가할 것이다. 이러한 일을 하기 위해서는 다음과 같은 3가지의 단계를 거쳐야 한다.

 

 

1. greeting.js 파일의 코드 첫 부분에 다음과 같은 코드가 존재해야 한다.

 

// greeting.js
var exports = module.exports = {};

 

 

 

 

2. 다른 파일에서 exports 객체를 사용하기를 원한다면 greeting.js 파일에서 다음과 같이 작성해야 한다.

 

// greetings.js
// var exports = module.exports = {};

exports.sayHelloInEnglish = function() {
    return "HELLO";
};
exports.sayHelloInSpanish = function() {
    return "Hola";
};

 

위의 코드에서 exports 를 module.exports 로 대체할 수 있으며 같은 결과를 얻을 수 있다.

이 부분이 잘 이해가 가지 않는다면 exports 와 module.exports 가 같은 객체를 참조한다고 기억하기 바란다.

 

 

 

 

3. module.exports 의 현재 값은 다음과 같습니다.

 

module.exports = {
    sayHelloInEnglish: function() {
        return "HELLO";
    },

    sayHelloInSpanish: function() {
        return "Hola";
    }
};

 

 

 

 

모듈 사용하기(importing)

main.js 라는 새로운 파일에서 greeting.js 의 메소드를 사용할 수 있도록 import 하는 과정은 다음과 같다.

 

1. 먼저 require 이라는 키워드는 Node.js 에서 module(모듈) 을 import(추가) 하기 위해 사용한다.. require 는 다음과 같이 정의되어 있다.

 

var require = function(path) {

 // ...

 return module.exports;
};

 

 

2. main.js 에서 greetings.js 를 require 합니다.

 

// main.js
var greetings = require("./greetings.js");

 

 

 

위의 코드는 아래와 동일한 코드이다.

 

// main.js
var greetings = {
    sayHelloInEnglish: function() {
        return "HELLO";
    },

    sayHelloInSpanish: function() {
        return "Hola";
    }
};

 

 

 

 

3. main.js 에서 greeting.js 의 값과 메소드에 접근할 수 있다.

 

// main.js
var greetings = require("./greetings.js");

// "Hello"
greetings.sayHelloInEnglish();

// "Hola"
greetings.sayHelloInSpanish();

 

 

 

중요 포인트

require 키워드는 object 를 반환한다.

그리고 module.exports 와 exports 는 call by reference (같은 주소값) 로 동일한 객체를 바라보고 있고, 리턴되는 값은 항상 module.exports 입니다.

 

 

모듈은 기본적으로 객체이고, 이 객체를 module.exports, exports 모두 바라보고 있는데, 최종적으로 return 되는 것은 무조건 module.exports 라는 것이다.

 

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res) {
    res.render('index', { title: 'Express' });
});

module.exports = router;

 

위의 소스는 다음과 같이 해석할 수 있다.

express.Router( ) 가 리턴한 "객체"에 일부 프로퍼티를 수정한 뒤, 이 객체 자체를 모듈로 리터난 것이다.

 

 

출처

https://jongmin92.github.io/2016/08/25/Node/module-exports_exports/

 

module.exports와 exports 차이 이해하기

모듈이란?모듈이란 관련된 코드들을 하나의 코드 단위로 캡슐화 하는 것을 말합니다. Node.js 에서 예시를 살펴보겠습니다.다음과 같은 greeting.js 라는 파일이 있습니다. 이 파일은 두개의 함수를

jongmin92.github.io

 

 

 

 

 

 

다른 예시

두가지의 파일이 있다.

 

// num.js 파일
const odd = '홀수입니다';
const even = '짝수입니다';

module.exports = { odd, even };





// fun.js 파일
const {odd, even} = require('./num');

function numTest(num) {
	if(num % 2 === 0) return even;
    else {
    return odd;
    }
}

module.exports = { odd, even, numTest };   // 이렇게 odd, even 을 한번 더 내보낼 수 있음

 

 

 

 

 


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

에러가 발생할 만한 곳은 try catch 로 감싼다.

 

// error1.js
setInterval(() => {
	console.log('시작');
    try {
    	throw new Error('서버를 고장내주마!');   // 에러를 throw 한 것을 catch 에서 받는다
    } catch (err){     // 에러가 아닌 것처럼 처리해준다.
    	console.err(err);
    }
}, 1000);

 

 

 

 

 


서버와 클라이언트

클라이언트가 서버로 요청을 보낸다.

서버는 요청을 처리하고

처리 후 클라이언트로 응답을 보낸다.

 

->

HTTP 프로토콜로 서로 요청과 응답을 주고 받는다.

클라이언트 : 서버한테 "메인페이지 주세요~~"

 

 

 

 

 

노드로 http 서버 만들기

createServer 로 요청 이벤트에 대기

req 객체는 요청에 관한 정보가, res 객체는 응답에 관한 정보가 담겨 있다.

 

const http = require('http');

http.createServer((req, res) => {     // 요청이 오면 실행
	// 여기에 어떻게 응답할지 적습니다.
});

 

 

 

 

 

 


/*
프로미스
콜백 헬이라고 불리는 지저분한 자바스크립트 코드의 해결책
프로미스 : 내용이 실행되었지만 결과를 아직 반환하지 않은 객체
then을 붙이면 결과를 반환함
실행이 완료되지 않았으면 완료된 후에 then 내부 함수가 실행됨

Resolve(성공리턴값) -> then으로 연결
Reject(실패리턴값)-> catch 로 연결
Finally 부분은 무조건 실행됨
*/

const condition = true; // true 면 resolve, false 면 reject
const promise = new Promise((resolve, reject) => {
	if(condition){
		resolve('성공');
	} else {
		reject('실패');
	}
});
// 여기엔 다른 코드가 들어갈 수 있음
promise
	.then((message) => {
		console.log(message); // 성공(resolve)한 경우 실행
	.catch((error)=> {
		console.error(error);
})



const promise = setTimeoutPromise(3000)   // 잠시 저장하고

console.log('딴짓');
console.log('딴짓');

// 딴짓하다가

promise.then(()=>{
// 지금할래
});

 

promise 를 어떨때 쓰냐..

"저 파일을 읽어와, 네이버에 요청을 한번 보냈다가 와" 

근데 그 요청이 항상 성공하는 것이 아님... 실패할 확률이 있다.....

 

 

비동기에는 항상 실패할 가능성을 염두해야 한다.

성공이면 resolve 호출

네이버가 날 거절하면 reject 호출

 

resolve 를 호출하면 then 으로 가고,

reject 를 호출하면 catch 로 간다.

 

 

 

async / await 

async function 의 도입

변수 = await 프로미스; 인 경우 프로미스가 resolve 된 값이 변수에 저장

변수 await 값; 인 경우 그 값이 변수에 저장

 

 

async function findAndSaveUser(Users){
	let user = await User.findOne({});
    	user.name = 'zero';
    	user = await user.save();
    	user = await User.findOne({ gender: 'm' });
    	// 생략
}

 

 

 

 

 

Promise 는 프로미스가 생성된 시점에는 알려지지 않았을 수도 있는 값을 위한 대리자로, 비동기 연산이

종료된 이후에 결과 값과 실패 사유를 처리하기 위한 처리기를 연결할 수  있습니다.

프로미스를 사용하면 비동기 메서드에서 마치 동기 메서드처럼 값을 반환할 수 있습니다.

다만 최종 결과를 반환하는 것이 아니고, 미래의 어떤 시점에 결과를 제공하겠다는 '약속(프로미스)'을

반환합니다.

 

 

 

 

생성자 = Promise( ) 

새로운 Promise 객체를 생성합니다. 주로 프로미스를 지원하지 않는 함수를 감쌀 때 사용합니다.

 

정적 메서드 = Promise.reject(reason)

주어진 사유로 거부하는 Promise 객체를 반환합니다.

 

정적 메서드2 = Promise.resolve( ) 

주어진 값으로 이행하는 Promise 객체를 반환합니다.

이때 지정한 값이 then 가능한 (then 메서드를 가지는) 값인 경우, Promise.resolve( ) 가 반환하는 

프로미스는 then 메서드를 "따라가서" 자신의 최종 상태를 결정합니다. 그 외의 경우, 반환된 프로미스는

주어진 값으로 이행합니다.

 

 

let myFirstPromise = new Promise((resolve, reject) => {
  // 우리가 수행한 비동기 작업이 성공한 경우 resolve(...)를 호출하고, 실패한 경우 reject(...)를 호출합니다.
  // 이 예제에서는 setTimeout()을 사용해 비동기 코드를 흉내냅니다.
  // 실제로는 여기서 XHR이나 HTML5 API를 사용할 것입니다.
  setTimeout( function() {
    resolve("성공!")  // 와! 문제 없음!
  }, 250)
})

myFirstPromise.then((successMessage) => {
  // successMessage는 위에서 resolve(...) 호출에 제공한 값입니다.
  // 문자열이어야 하는 법은 없지만, 위에서 문자열을 줬으니 아마 문자열일 것입니다.
  console.log("와! " + successMessage)
});

 

 

 

 

async & await 실용 예제

async & await 문법이 가장 빛을 발하는 순간은 여러 개의 비동기 처리 코드를 다룰 때이다.

아래와 같이 각각 사용자와 할 일 목록을 받아오는 HTTP 통신 코드가 있다고 하자.

 

function fetchUser() {
  var url = 'https://jsonplaceholder.typicode.com/users/1'
  return fetch(url).then(function(response) {
    return response.json();
  });
}

function fetchTodo() {
  var url = 'https://jsonplaceholder.typicode.com/todos/1';
  return fetch(url).then(function(response) {
    return response.json();
  });
}

 

위 함수들을 실행하면 각각 사용자 정보와 할 일 정보가 담긴 프로미스 객체가 반환된다.

 

자 이제 이 두 함수를 이용하여 할 일 제목을 출력해보겠다. 살펴볼 예제 코드의 로직은 아래와 같다.

 

1. fetchUser( ) 를 이용하여 사용자 정보 호출

2. 받아온 사용자 아이디가 1이면 할 일 정보 호출

3. 받아온 할 일 정보의 제목을 콘솔에 출력

 

 

async function logTodoTitle() {
  var user = await fetchUser();
  if (user.id === 1) {
    var todo = await fetchTodo();
    console.log(todo.title); // delectus aut autem
  }
}

 

 

logTodoTitle( ) 를 실행하면 콘솔에 deletus aut autem 가 출력될 것이다.

위 비동기 처리 코드를 만약 콜백이나 프로미스로 했다면 훨씬 더 코드가 길어졌을 것이고 가독성도 좋지 않았을 거다.

 

 

이처럼 async await 문법을 이용하면 기존의 비동기 처리 코드 방식으로 사고하지 않아도 되는 장점이 생긴다.

 

 

 

 

 

 

 

 

async & await 예외 처리

async & await 에서 예외를 처리하는 방법은 바로 try & catch 이다.

프로미스에서 에러 처리를 위해 .catch( ) 를 사용했던 것처럼 async 에서는 catch { } 를 사용하면 된다.

 

async function logTodoTitle() {
  try {
    var user = await fetchUser();
    if (user.id === 1) {
      var todo = await fetchTodo();
      console.log(todo.title); // delectus aut autem
    }
  } catch (error) {
    console.log(error);
  }
}

 

위의 코드를 실행하다가 발생한 네트워크 통신 오류뿐만 아니라 간단한 타입 오류 등의 일반적인 오류까지도 catch로 잡아낼 수 있다. 발견된 에러는 error 객체에 담기기 때문에 에러의 유형에 맞게 에러 코드를 처리해주시면 된다.

 

 

 

 

참고 출처

https://joshua1988.github.io/web-development/javascript/js-async-await/

 

자바스크립트 async와 await

(중급) 자바스크립트 개발자를 위한 async, await 사용법 설명. 쉽게 알아보는 자바스크립트 async await 개념, 사용법, 예제 코드, 예외 처리 방법

joshua1988.github.io

 

 


Node.js 디렉터리 구조

 

├───models
│   ├───user.model.js
├───routes
│   ├───user.route.js
├───services
│   ├───user.service.js
├───controllers
│   ├───user.controller.js

 

모듈식 코드 구조의 경우 논리는 이러한 디렉터리와 파일로 나누어 져야 합니다.

 

모델 - 모델의 스키마 정의

경로 - API 경로가 컨트롤러에 매핑

컨트롤러 - 컨트롤러는 요청 매개 변수 확인, 쿼리, 올바른 코드로 응답 보내기의 모든 로직을 처리합니다.

서비스 - 서비스에는 데이터베이스 쿼리와 반환 객체 또는 오류 발생이 포함됩니다.

 

 

 

 

 

 

모델 - 경로 - 컨트롤러 - 서비스 코드 구조

 

user.model.js

 

var mongoose = require('mongoose')

const UserSchema  = new mongoose.Schema({
    name: String
})

const User = mongoose.model('User', UserSchema)

module.exports = User;

 

 

user.routes.js

 

var express = require('express');
var router = express.Router();

var UserController = require('../controllers/user.controller')

router.get('/', UserController.getUsers)

module.exports = router;

 

 

 

 

user.controllers.js

 

var UserService = require('../services/user.service')    

exports.getUsers = async function (req, res, next) {
    // Validate request parameters, queries using express-validator
    
    var page = req.params.page ? req.params.page : 1;
    var limit = req.params.limit ? req.params.limit : 10;
    try {
        var users = await UserService.getUsers({}, page, limit)
        return res.status(200).json({ status: 200, data: users, message: "Succesfully Users Retrieved" });
    } catch (e) {
        return res.status(400).json({ status: 400, message: e.message });
    }
}

 

 

 

 

user.services.js

 

var User = require('../models/user.model')

exports.getUsers = async function (query, page, limit) {

    try {
        var users = await User.find(query)
        return users;
    } catch (e) {
        // Log Errors
        throw Error('Error while Paginating Users')
    }
}

 

 

서비스에서 쿼리를 불러오고

컨트롤러에서 서비스 쿼리를 컨트롤..

 

 

 

 

 

 

 


게시판 CRUD

board-route

 

var express = require('express')
var router = express.Router()
const BoardController = require('../controllers/board-controller')

router.get('/:boardId', BoardController.getBoard)
router.get('/', BoardController.getBoards)
router.post('/', BoardController.insertBoard)
router.patch('/:boardId', BoardController.updateBoard)
router.delete('/:boardId', BoardController.deleteBoard)
router.get('/:boardId/comment', BoardController.getComments)
router.post('/:boardId/comment', BoardController.insertComment)
router.patch('/:boardId/comment/:commentId', BoardController.updateComment)
router.delete('/:boardId/comment/:commentId', BoardController.deleteComment)

module.exports = router

 

 

 

 

 

board-controller

 

const BoardService = require('../services/board-service')

exports.getBoard = async (req, res, next) => {
    let { boardId } = req.params
    try {
        let rows = await BoardService.getBoard(boardId)
        return res.json(rows[0])
    } catch (err) {
        return res.status(500).json(err)
    }
}

// 생략...

exports.deleteComment = async (req, res, next) => {
    let { boardId, commentId } = req.params
    try {
        let del = await BoardService.deleteComment(boardId, commentId)
        return res.json(del)
    } catch (err) {
        return res.status(500).json(err)
    }
}

 

 

 

 

 

board-service

 

const pool = require('../database/pool')
const BoardQuery = require('../queries/board-query')

exports.getBoard = async (boardId) => {
    try {
        let data = await pool.query(BoardQuery.getBoard, [boardId])
        return data[0]
    } catch (err) {
        console.log(err)
        throw Error(err)
    }
}

// 생략...

exports.deleteComment = async (boardId, commentId) => {
    let conn = await pool.getConnection()
    try {
        await conn.beginTransaction()

        let del = await conn.query(BoardQuery.deleteComment, [commentId])
        if (del[0].affectedRows == 1) {
            let upd = await conn.query(BoardQuery.minusCommentCnt, [boardId])
        }
        await conn.commit()

        return del[0]
    } catch (err) {
        conn.rollback()
        console.log(err)
        throw Error(err)
    } finally {
        conn.release()
    }
}

 

 


process.env

시스템 환경변수들이 들어있는 객체

비밀키(데이터베이스 비밀번호, 서드파티 앱 키 등)를 보관하는 용로도로 쓰임

환경변수는 process.env 로 접근 가능

 

const secretId = process.env.SECRET_ID; // 이렇게 비밀키를 가리는 용도로 쓰임. 직접 쓰면 위험
const secretCode = process.env.SECRET_CODE;

 

일부 환경 변수는 노드 실행 시 영향을 미침

예시) NODE_OPTIONS(노드 실행 옵션), UV_THREADPOOL_SIZE(스레드풀 개수)

max-old-space-size: 노드가 사용할 수 있는 메모리를 지정하는 옵션

 

 

 

ex)

NODE_OPTIONS = --max-old-space-size=8192 ...

 

 

 

 

 

 

 

'🌿 Node' 카테고리의 다른 글

express 공부  (0) 2022.02.20
espress 기초 다지기 - 미들웨어편  (0) 2022.02.20
express 파일 구조와 몽고 디비  (0) 2022.02.16
노드 이해하기2  (1) 2022.02.13
GET, POST, DELETE, PUT  (0) 2022.02.13