expressjs/body-parser

Node.js body parsing middleware. Contribute to expressjs/body-parser development by creating an account on GitHub.

github.com

 

What are express.json() and express.urlencoded()?

I cannot find any documentation on express.json() and express.urlencoded(). What do each of them do exactly?

stackoverflow.com

 

1. Parsing 이란?

 

parsing : 가지고 있는 데이터를 내가 원하는 형태의 데이터로 ‘가공'하는 과정

parser : 파싱 과정을 하는 모듈이나 메서드를 일컫는다. 

 

내가 원하는 형식에 맞춰 해석하는 용도이므로 bodyParser 뿐만 아니라 cookieParser, JSON.parse, JSON.stringifyJSON.stringify.. 등 파서의 종류는 셀 수 없이 많다.

예를 들어 외국어를 번역할 때 어떤 언어인지 정하고 해당 언어에 맞게 구문을 해석해주는 것을 parser, 그에 따라 실제 번역하는 것을 compiler라고 할 수 있다.

 

 

 

 

2. BodyParser

HTTPpost put 요청 시 request body에 들어오는 데이터 값을 읽을 수 있는 구문으로 파싱함과 동시에
req.body 로 입력해주어 응답 과정에서 요청에 body 프로퍼티를 새로이 쓸 수 있게 해주는 미들웨어

 

클라이언트 측에서 API post와 put 메서드로 요청 시 (get delete는 불가능) body를 포함하여 보낼 수 있는데 이 값을 서버 측에서 받는다고 그대로 사용할 수 있는 것이 아니고, 서버 내에서 해석 가능한 형태로 변형해야 사용할 수 있게 되는 것이다.
이때 API 요청에서 받은 body 값을 파싱하는 역할을 수행하는 것이 bodyParser라는 미들웨어이다.

 

 

설치

npm insatll body-parser --save

 

예제

<form action="/email_post" method="post">
      email: <input type="text" name="email" /><br />
      <input type="submit" />
</form>

 

- body-parser 사용하지 않은 경우

// body-parser 없이 사용하면
app.post('/email_post', function (req, res) {
  console.log(req.body);
  res.send('post response');
});

// undefined 출력된다.

 

- body-parser 사용한 경우

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

 

post나 put으로 넘어온 데이터 중 json형식이나 encoding 된 url 데이터를 분석하도록  express 서버에 설정해주는 구문이다.

 

const express = require('express');
const app = express();
const bodyParser = require('body-parser');


// 클라이언트에서 오는 응답이 json형태 or 
// json이 아닌 그냥 post(urlencoding된) 데이터를 파싱
app.use(express.json()) // for parsing application/json
app.use(express.urlencoded({ extended: true })) // for parsing application/x-www-form-urlencoded

app.post('/email_post', function (req, res) {
  console.log(req.body);
  console.log(req.body.email);
  res.send('post response');
});

/*
출력
  { email: 'asd@asdasdasd.com' }
  'asd@asdasdasd.com'
*/

 

express 문서에 따르면 미들웨어 없이 req.body 에 접근하는 경우에는 기본으로undefined 가 설정되어 있으므로 bodyParser, multer와 같은 미들웨어를 사용하여 요청 데이터 값에 접근해야 한다고 되어있다.

클라이언트 측에서 { name: 'John', age:...}와req.body 혹은 req.body.name, req.body.job 등으로 해당 데이터에 곧바로 접근할 수 있게 된다.

반응형

'Node.js > Express' 카테고리의 다른 글

[Express] express.Router() 와 express() 차이  (0) 2020.11.05
 

http-proxy-middleware

The one-liner node.js proxy middleware for connect, express and browser-sync

www.npmjs.com

 

1. 설치

$ npm install http-proxy-middleware --save-dev

 

 

2. 사용법

- client 단 src파일 아래 'setupProxy.js' 파일을 생성한다.

- 다음과 같이 proxy 설정을 한다.

 

1) http-proxy-middleware v1.0 이전

const proxy = require('http-proxy-middleware');

// src/setupProxy.js
module.exports = function(app) {
    app.use(
        proxy('/api', {
            target: "http://localhost:5000/", 
            changeOrigin: true
        })
    );
};

proxy 설정을 추가해줌으로 /api로 시작되는 API는 target으로 설정된 서버 URL로 호출하도록 설정된다.

 

 

2) http-proxy-middleware v1.0 이후 수정

const createProxyMiddleware = require('http-proxy-middleware');

// src/setupProxy.js
module.exports = function(app) {
    app.use(
        createProxyMiddleware('/api', {
            target: "http://localhost:5000/", 
            changeOrigin: true
        })
    );
};

 

- 기본형태

const { createProxyMiddleware } = require('http-proxy-middleware');
 
const apiProxy = createProxyMiddleware('/api', { target: 'http://www.example.org' });
//                                      		\____/   \_____________________________/
//                                       	 	  |                    |
//                                      		context             options
 
// 'apiProxy' is now ready to be used as middleware in a server.

context로 설정한 주소가 tartget으로 설정한 서버 쪽 url로 proxing 된다. 

여러 옵션들은 차차 필요할 때 사용하면 될 듯하다.

반응형

 

 

Differences between express.Router and app.get?

I'm starting with NodeJS and Express 4, and I'm a bit confused. I been reading the Express website, but can't see when to use a route handler or when to use express.Router. As I could see, if I wa...

stackoverflow.com

 

 

app.js
var express = require('express'),
    dogs    = require('./routes/dogs'), // 모듈로 만든 라우터들을 사용
    cats    = require('./routes/cats'),
    birds   = require('./routes/birds');

var app = express();

app.use('/dogs',  dogs);
app.use('/cats',  cats);
app.use('/birds', birds);

app.listen(3000);

 

use메서드는 모든 HTTP 메서드에 대해 요청 주소만 일치하면 실행되지만

get, post, put, patch, delete 같은 메서드는 주소뿐만 아니라 HTTP 메서드까지 일치하는 요청일 때만 실행된다. 

- express()는 listen()을 이용해 수신한다.

 

 

 

routes/dog.js
var express = require('express');

var router = express.Router();

router.get('/', function(req, res) {
    res.send('GET handler for /dogs route.');
});

router.post('/', function(req, res) {
    res.send('POST handler for /dogs route.');
});

module.exports = router; // 라우터를 모듈화

 

router에도 app처럼 use, get, post, put, patch, delete 같은 메서드를 붙일 수 있다. use를 제외하고는 각각 HTTP 요청 메서드와 상응한다.

라우터에서는 반드시 요청에 대한 응답을 보내거나 에러 핸들러로 요청을 넘겨야 한다. 응답을 보내지 않으면 브라우저는 계속 응답을 기다린다. res객체에 들어 있는 메서드들로 응답을 보낸다.

- Router는 요청에 대해 listen()을 사용하지 않는다.

 

 

두 가지 경우 차이점

var app = express()가 호출되면 앱 객체가 반환된다. 이것을 Main app으로 생각한다.
var router = express.Router()가 호출되면 Main app과는 조금 다른 Mini app이 반환된다.

 

코드(미들웨어)가 복잡해지는 것을 방지하고 재사용성을 높이기 위해 각 라우터를 별도의 모듈로 만드는 것이다. 즉, 각 기능에 맞는 Mini app을 만들고 그것들을 Main app에서 불러와 사용하는 것이다.

 

위의 예에서, /dogs 경로에 대한 코드는 메인 앱을 복잡하게 하지 않도록 자체 파일로 이동했다. /cats와 /birds에 대한 코드는 그들 자신의 파일에서 유사한 구조가 될 것이다. 이 코드를 세 개의 미니 앱으로 분리하면 각각을 위한 logic 작업을 따로 할 수 있고, 나머지 두 개에 어떤 영향을 미칠지 걱정하지 않아도 된다.

 

반응형

'Node.js > Express' 카테고리의 다른 글

[Express] Body-Parser 미들웨어 다루기  (0) 2020.12.04
 

mongoose

Mongoose MongoDB ODM

www.npmjs.com

 

1. Mongoose ?

Node.js 비동기 환경에서 동작하도록 디자인 된 몽고DB 모델링 툴이다. Mongoose는 promise와 callback 모두 지원한다.

 

2. 설치

$ npm install mongoose --save

 

3. 불러오기(importing)

// Using Node.js `require()`
const mongoose = require('mongoose');
 
// Using ES6 imports
import mongoose from 'mongoose';

 

4. 몽고DB 연결

만약 하나의 DB만 사용한다면, mongoose.connect()를 사용하고, 만약 추가적인 연결들이 필요할 경우에 mongoose.createConnection()을 사용한다. 두 가지 경우 모두 mongodb://URI 또는 host, database, port, options 같은 파라미터를 받습니다.

 

// npm공식 문서 사용 예시
mongoose.connect('mongodb://localhost/my_database', {
  useNewUrlParser: true,
  useUnifiedTopology: true,
  useFindAndModify: false,
  useCreateIndex: true
});

 

// 사용 예시
// config.mongoURI --> 몽고DB url을 외부 모듈(config라는 명)로 만들어 불러서 사용한 경우
mongoose.connect(config.mongoURI, {
  useNewUrlParser: true, 
  useUnifiedTopology: true, 
  useCreateIndex: true, 
  useFindAndModify: false
})
.then(() => console.log('MongoDB Conneted...'))
.catch(err => console.log(err));

 

여기서 몽고 DB 가 Local 일 경우 주소만 해당되지만 클라우드를 통해서 올 때는 비밀번호까지 들어가기 때문에 조심해야 한다.

 

 

5. 스키마 정의 & 모델화

 

var mySchema = mongoose.Schema({
    name : 'string',
    address : 'string',
    age : 'number'
});
// mongoose.model('ModelName', mySchema)
const MyModel = mongoose.model('ModelName');

정의된 스키마를 객체처럼 사용할 수 있도록 model() 함수로 컴파일한다.

 

 

6. 모델 객체 생성 후 데이터 입력

const newInfo = new MyModel({name:'Hong Gil Dong', address:'서울시 강남구', age:'26'});

new를 이용해 모델 객체를 생성해 데이터를 입력해준다.

 

7. 데이터 저장

newInfo.save(function(error, data){
    if(error){
        console.log(error);
    }else{
        console.log('Saved!')
    }
});
  • Model.save(function(err,product)); : Callback

  • Model.save().then(resolved,rejected); : Promise

 

8. 데이터 가져오기

// find(): 전체 데이터 불러오기, findOne() : 데이터 하나 가져오기
MyModel.find({}, function (err, docs) {
  // docs.forEach
});

// 예제
newInfo.find(function(error, students){
    console.log('--- Read all ---');
    if(error){
        console.log(error);
    }else{
        console.log(students);
    }
})

 

 

9. 데이터 수정하기

// 특정 id에 해당하는 데이터 수정
MyModel.findOne({_id:'585b777f7e2315063457e4ac'}, function(error,student){
    if(error){
        console.log(error);
    }else{
        student.name = '김철수';
        student.save(function(error,modified_student){
            if(error){
                console.log(error);
            }else{
                console.log(modified_student);
            }
        });
    }
});

// update() 이용방법도 있다.

 

 

10. 데이터 삭제하기

MyModel.findOne({title:'아바타'}, function(err, doc) {
   if ( doc ) {
      doc.name = '홍길동';
      doc.remove(function(err, product) {
         console.log('Find and Remove : ', err, product);
      });         
   }
});

// remove()함수 이용 
MyModel.remove({job:'학생'}).then(resolved, rejected);
반응형
Express 공식 문서를 참조했습니다.

1. Express?

: Node.js를 위한 빠르고 개방적인 웹 프레임워크, 최소한의 기능을 갖춘 라우팅 및 미들웨어 웹 프레임워크

 

2. 설치

npm install express --save

npm install express

 

3. 기본 사용법

const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

 

4. 라우팅

 

1) 구조

app.METHOD(PATH, HANDLER)

- app : express() 객체 인스턴스

- METHOD : HTTP 요청 메소드(get, post, put, delete...)

- PATH : 서버에서의 경로 url 

- HANDLER : 라우트가 성공했을 때 실행되는 함수

 

2) 예제

- 라우트 메소드

var express = require('express');
var app = express();
// 홈 화면으로 라우팅 되며, hello world가 출력
app.get('/', function(req, res) {
  res.send('hello world');
});

// GET method route
app.get('/', function (req, res) {
  res.send('GET request to the homepage');
});

// POST method route
app.post('/', function (req, res) {
  res.send('POST request to the homepage');
});

 

- 라우트 경로 

 

요청 메소드와의 조합을 통해, 요청이 이루어질 수 있는 엔드포인트를 정의한다. 라우트 경로는 문자열, 문자열 패턴 또는 정규식 일 수 있다.

 

- 라우트 핸들러

하나의 콜백 함수는 하나의 라우트를 처리할 수 있다. 예를 들면 다음과 같다.

app.get('/example/a', function (req, res) {
  res.send('Hello from A!');
});

미들웨어와 비슷하게 작동하는 여러 콜백 함수를 제공하여 요청을 처리할 수 있다. 유일한 차이점은 이러한 콜백은 next('route')를 호출하여 나머지 라우트 콜백을 우회할 수도 있다는 점이다. 이러한 메커니즘을 이용하면 라우트에 대한 사전 조건을 지정한 후, 현재의 라우트를 계속할 이유가 없는 경우에는 제어를 후속 라우트에 전달할 수 있다.

 

// 2개 이상의 콜백 함수는 하나의 라우트를 처리할 수 있다(next 오브젝트를 반드시 지정해야 함)
app.get('/example/b', function (req, res, next) {
  console.log('the response will be sent by the next function ...');
  next();
}, function (req, res) {
  res.send('Hello from B!');
});
// 하나의 콜백 함수 배열은 하나의 라우트를 처리할 수 있다.
var cb0 = function (req, res, next) {
  console.log('CB0');
  next();
}

var cb1 = function (req, res, next) {
  console.log('CB1');
  next();
}

var cb2 = function (req, res) {
  res.send('Hello from C!');
}

app.get('/example/c', [cb0, cb1, cb2]);
// 독립적인 함수와 함수 배열의 조합은 하나의 라우트를 처리할 수 있다.
var cb0 = function (req, res, next) {
  console.log('CB0');
  next();
}

var cb1 = function (req, res, next) {
  console.log('CB1');
  next();
}

app.get('/example/d', [cb0, cb1], function (req, res, next) {
  console.log('the response will be sent by the next function ...');
  next();
}, function (req, res) {
  res.send('Hello from D!');
});

 

 

5. 미들웨어

 

1) 작성

<출처: https://expressjs.com>

2) 예제

var express = require('express');
var app = express();

// 미들웨어 함수 작성 후 변수 할당
var requestTime = function (req, res, next) {
  req.requestTime = Date.now();
  next();
};

// 미들웨어 함수를 로드하려면 미들웨어 함수를 지정하여 app.use()를 호출
// 루트 경로 라우트 로드 전에 먼저 로드 된다.
app.use(requestTime);

// 콜백 함수는 미들웨어 함수가 req(요청 오브젝트)에 추가하는 특성을 사용한다.
app.get('/', function (req, res) {
  var responseText = 'Hello World!';
  responseText += 'Requested at: ' + req.requestTime + '';
  res.send(responseText);
});

app.listen(3000);

 

- app.use() : 사용하고자 하는 미들웨어 함수를 로드한다.

 

미들웨어의 로드 순서는 중요하며, 먼저 로드되는 미들웨어 함수가 먼저 실행된다.

만약 위 예제에서 루트 경로 라우링 이후 app.use()를 하면 루트 경로의 라우트 핸들러가 요청-응답 주기를 종료하므로 요청은 절대로 requestTime에 도달하지 못한다. (스택 내의 그다음 미들웨어 함수에 요청을 전달하는 next() 요청이 없기 때문이다.)

 

현재의 미들웨어 함수가 요청-응답 주기를 종료하지 않는 경우에는 next()를 호출하여 그 다음 미들웨어 함수에 제어를 전달해야 합니다. 그렇지 않으면 해당 요청은 정지된 채로 방치됩니다.

 

사용자는 요청 오브젝트, 응답 오브젝트, 스택 내의 그 다음 미들웨어 함수, 그리고 모든 Node.js API에 대한 액세스 권한을 가지고 있으므로, 미들웨어 함수에 대한 가능성, 중요성은 끝이 없다고 설명돼있다.

 

3) 여러 미들웨어 함수 종류

<출처: https://expressjs.com/ko/resources/middleware.html>

이 외에도 유명한 여러 미들웨어가 많으니 필요에 따라 사용하면 될 듯하다.

반응형

1. 라우팅 테스트

const express = require('express');
const jwt = require('jsonwebtoken');

const app = express();

app.get('/api', (req, res) => {
  res.json({
    message: 'Welcome to the API'
  });
});



app.listen(5000, () => console.log('Server started on port 5000'));

- express: 프로젝트에서 사용할 웹서버 프레임워크

- jsonwebtoken: 이 예제프로젝트에서 사용되는 핵심 모듈이다. JSON Web Token을 손쉽게 생성하고, 또 검증도 해준다.

 

'/api' URL로 get 요청을 하면 성공적으로 메시지가 나오게 된다.

 

 

 

2. 사용자 정보 암호화 토큰 생성

app.post('/api/login', (req, res) => {
  // Mock user
  const user = {
    id: 1,
    username: 'brad',
    email: 'bread@gmail.com'
  }

# 사용자 정보 암호화 - sign(전달할 내용, 비밀 키, 유효시간, 콜백)
  jwt.sign({ user }, 'secretkey', { expiresIn: '30s' }, (err, token) => {
    res.json({
      token
    });
  });
});

 

jwt.sign(payload, secretOrPrivateKey, [options, callback])

- payload : 객체 리터럴, 버퍼 또는 유효한 JSON을 나타내는 문자열. 여기선 임시 유저 정보를 넣어줬다.

- 두 번째 인자 : 비밀 키 전달

- 세 번째 인자 : 토큰에 대한 정보를 객체로 전달

- 네 번째 인자 : 콜백함수 작성. 만약 콜백 함수를 작성하지 않으면 동기 처리가 된다.

 

 

 

3. 토큰 해싱 작업 

app.post('/api/posts', verifyToken, (req, res) => {
# 암호 토큰 해싱작업
  jwt.verify(req.token, 'secretkey', (err, authData) => {
    if (err) {
      res.sendStatus(403);
    } else {
      res.json({
        message: 'Post created...',
        authData
      });
    }
  });
});

function verifyToken(req, res, next) {
  // Get auth header value
  const bearerHeader = req.headers['authorization'];
  // Check if bearer is undefined
  if (typeof bearerHeader !== 'undefined') {
    // Split at the space
    const bearer = bearerHeader.split(' ');
    // Get token from array
    const bearerToken = bearer[1]; // 토큰 값
    // Set the token
    req.token = bearerToken;
    // Next middleware
    next();

  } else {
    // Forbidden
    res.sendStatus(403);
  }
}

 

jwt.verify(token, secretOrPublicKey, [options, callback]) 

: jsonwebtoken 모듈에서 권한을 확인하는 메서드

 

- 첫 번째 인자 : 토큰 값을 전달

  • 인증이 된 유효한 토큰인지 확인하기 위한 것

  • 토큰은 쿠키에 저장되어 있으므로 요청 객체에서 cookies 속성을 확인하면 된다. ( express에서는 자동으로 cookieparser 미들웨어가 등록되어 있다. )

- 두 번째 인자 : 토큰 생성 시에 사용한 비밀 키를 디코딩하기 위해 똑같이 사용한다.

- 네 번째 인자 : 콜백함수 유무에 따라 비동기, 동기로 동작한다. 

 

위의 코드에서 로그인한 사용자는 JWT토큰을 생성받았기에 어떤 API를 사용할 권리가 있게 된다.

/api/posts URL로 요청하면, 쿠키에서 토큰 값을 읽고 verify() 함수를 호출해 json객체를 반환한다.

 

 

 

<전체 코드>

 

const express = require('express');
const jwt = require('jsonwebtoken');


const app = express();

app.get('/api', (req, res) => {
  res.json({
    mesaage: 'Welcom to the API'
  });
});

app.post('/api/posts', verifyToken, (req, res) => {
  jwt.verify(req.token, 'secretkey', (err, authData) => {
    if (err) {
      res.sendStatus(403);
    } else {
      res.json({
        mesaage: 'Post Created..',
        authData
      });
    }
  });

});

app.post('/api/login', (req, res) => {
  // Mock user
  const user = {
    id: 1,
    username: 'brad',
    email: 'brad@gmail.com'
  }

  jwt.sign({ user: user }, 'secretkey', { expiresIn: '30s' }, (err, token) => {
    res.json({
      token // token: token
    });
  });
});

// FORMAT OF TOKEN
// Authorization: Bearer <access_token>

// Verify Token
function verifyToken(req, res, next) {
  // Get auth header value
  const bearerHeader = req.headers['authorization'];
  // Check if bearer is undefined
  if (typeof bearerHeader !== 'undefined') {
    // Split at the space
    const bearer = bearerHeader.split(' ');
    // Get token from array
    const bearerToken = bearer[1];
    // Set the token
    req.token = bearerToken;
    // Next middlewear
    next();
  } else {
    // Forbidden
    res.sendStatus(403);
  }
}

app.listen(5001, () => console.log('Server started on port 5001'));
반응형

 

모든 npm 패키지에는 보통 프로젝트 루트에 package.json이라는 파일이 들어 있다. 이 파일에는 프로젝트와 관련된 다양한 메타데이터가 저장되어 있다. 이 파일은 프로젝트의 종속성(dependencies)을 처리할 뿐만 아니라 프로젝트를 식별할 수 있는 정보를 npm에 제공하는 데 사용된다. 또한 프로젝트 설명, 특정 배포의 프로젝트 버전, 라이선스 정보, 구성 데이터 등과 같은 다른 메타데이터도 포함할 수 있으며, 이 모든 메타데이터는 npm과 패키지의 최종 사용자에게 필수적일 수 있다. package.json 파일은 일반적으로 Node.js 프로젝트의 루트 디렉터리에 위치한다.

 

 

Node.js 자체는 패키지의 다음의 두 필드만 인식한다.

{
  "name" : "프로젝트 이름",
  "version" : "0.0.0",
}

 

- name : 해당 프로젝트의 이름을 작성해주면 된다.

- version : 올바른 버전의 패키지가 설치되고 있는지 확인하기 위해 npm에서 사용한다.

일반적으로 major.minor.patch의 형태를 취하는데, major, minor, patch는 major, minor, patch는 매 신규 출시 후 증가하는 정수다.

 

 

 

{
  "name" : "underscore",
  "description" : "JavaScript's functional programming helper library.",
  "homepage" : "http://documentcloud.github.com/underscore/",
  "keywords" : ["util", "functional", "server", "client", "browser"],
  "author" : "Jeremy Ashkenas <jeremy@documentcloud.org>",
  "contributors" : [],
  "dependencies" : [],
  "repository" : {"type": "git", "url": "git://github.com/documentcloud/underscore.git"},
  "main" : "underscore.js",
  "version" : "1.1.6"
}

 

 

보시다시피 프로젝트의 설명(description) 및 키워드(keywords) 필드가 있다. 이것은 당신의 프로젝트를 찾은 사람들이 그것이 무엇인지 단 몇 마디 말로 이해할 수 있게 해준다. 작성자(author), 기고자(contributors), 홈페이지(homepage), 리포지토리(repository) 분야는 모두 프로젝트에 기여한 사람들을 신용하고, 작성자/유지인에게 연락하는 방법을 보여주며, 추가 참조를 위한 링크를 제공하는 데 사용할 수 있다.

 

main 필드에 나열된 파일은 라이브러리의 주요 진입점이며, 누군가 require(<library name>) 으로 실행하려면 require(<package.json:main>) 식으로 호출해 라이브러리를 사용할 수 있다. dependencies 필드는 npm에서 사용할 수 있는 프로젝트의 모든 dependencies을 나열하는 데 사용된다. 누군가가 npm을 통해 당신의 프로젝트를 설치하면, 나열된 모든 dependencies 또한 설치될 것이다. 또한 다른 사용자가 프로젝트의 루트 디렉토리에서 npm 설치를 실행하면 ./node_modules에 모든 dependencies을 설치하게 된다.

 

devDependencies 필드를 package.json에 추가할 수도 있다. 이러한 종속성은 정상 작동에 필요한 것이 아니라 프로젝트를 패치하거나 수정하려는 경우 필수/권고 사항이다. 예를 들어, 테스트 프레임워크를 사용하여 장치 테스트를 작성한 경우 devDependency 필드에 사용한 테스트 프레임워크를 넣는 것이 적절할 것이다. 프로젝트의 devDependencies를 설치하려면 npm 설치를 사용할 때 --dev 옵션을 넣으면된다.

반응형

+ Recent posts