express는 node.js에서 빠르고 유연한 웹 어플리케이션 프레임워크로 서버 애플리케이션을 구축할때 필요한 기능과 도구를 제공한다.
import express from 'express';
const app = express();
// 라우트들..
app.listen(3000, () => {
console.log('Server OPEN');
})
특정 엔드포인트를 담당하는 코드이다.
express라우트는 아래와 같이 정의 할 수 있다.
app.method(path, handler)
1. method : http메소드 이름
2. path : 엔드포인트 경로
3. handler : 리퀘스트 로직을 처리하고 리스폰스를 돌려주는 핸들러 함수. 첫번째 파라미터로 리퀘스트 객체 두번째 파라미터로 리스폰스 객체를 받는다.
req.query
쿼리스트링 파라미터를 프로퍼티로 담고 있는 객체이다. (파라미터는 항상 문자열)
예시 : GET /some/path?foo=1&bar=2
app.get('/some/path', (req, res) => {
console.log(req.query); // {foo : '1', bar : '2'}
})
req.params
URL파라미터를 프로퍼티로 담고 있는 객체이다. (파라미터는 항상 문자열)
예시 : GET /some/1/path/james
app.get('some/:id/path/:name', (req, res) => {
console.log(req.params); // {id : '1', name : 'james'}
})
req.body
리퀘스트 바디 내용을 담고 있는 객체이다. 바디 내용을 req.body로 접근하려면
app.use(express.json())
라는 코드가 필요하다.
예시 : POST /some/path
# body내용
{
"field1" : "value1",
"field2" : "value2"
}
app.use(express.json());
app.post('/some/path', (req, res) => {
console.log(req.body); // {"field1" : "value1","field2" : "value2"}
})
res.send()
리스폰스를 보낸다. 아규먼트로 전달되는 값에 따라 Content-Type 헤더를 설정하고 적절한 바디 내용으로 변환해준다.
api서버를 만들때는 주로 객체나 배열을 전달하는데, 그러면 Content-Type헤더를 application/json으로 설정하고 객체나 배열을 JSON문자열로 바꿔서 전달해준다. 디폴트 상태 코드는 200(OK)이다.
res.status()
리스폰스의 상태코드를 설정한다.
res.status(404).send(...)
res.sendStatus()
리스폰스로 바디 없이 상태코드만 보낸다.
res.sendStatus(204)
tip :
npm install -g nodemon: -g옵션은 전역설치로 어디서나 사용이 가능하고, 전역설치는 컴퓨터 전체에 설치되며, 특정 프로젝트에 한정되지 않음.
tip :npm install --save-dev nodemon: 해당프로젝트에서만 사용이 가능하고, 로컬설치는 프로젝트 폴더 내 node_modules에 설치되며, package.json파일에 dependencies등으로 기록된다.
express프로그램 안에서 mongoDB를 사용하기 위해선 mongoose라는 라이브러리를 활용해야 한다. (자바스크립트를 사용해서 MongoDB데이터베이스와 소통할 수 있게 해주는 라이브러리)
npm install mongoose@버전
npm install dotenv
그리고 .env파일내에 DATABASE_URL과 같은 배포시에, 함께 배포되면 안되는 코드를 넣어준다.
# .env파일
DATABASE_URL = 'mongodb+srv://yab0403:<password>@cluster0.xbn5s.mongodb.net/<프로젝트명>?retryWrites=true&w=majority&appName=Cluster0'
그리고, 기준이 되는 app.js파일에 연결코드를 작성해주면 된다.
# app.js
import mongoose from 'mongoose';
import * as dotenv from 'dotenv';
mongoose.connect(process.env.DATABASE_URL).then(() => console.log('Connected DB'));
이렇게 mongoose.connect()메소드에 DATABASE_URL을 전달해서 데이터베이스에 접속할수있다.
이 작업은 비동기로 이루어지기 때문에, .then()메소드를 활용해서 DB에 접속이 되면 Connected DB라는 메세지를 출력시키는 코드이다.
mongoose의 .sort()메소드 : 정렬은 객체형태로 필드와 정렬 순서를 지정하여 사용된다.예를 들어,
{field : -1}은 내림차순, {filed : 1}은 오름차순이 된다.
.find()메소드 : 모든 객체를 조회하는데, .find()메소드에 필터객체를 전달하면 조건을 만족하는 데이터만 조회할 수 있다.
Example.find({name : 'James'}); // name이 James인 사람만 조회
Example.find({age : 30}); // age가 30인 사람만 조회
비교연산자
필드가 특정 값을 초과하는지 확인하려면 $gt
필드가 특정 값의 미만인지 확인하려면 $lt
연산자를 사용하는 경우, 객체안에 네스팅한다. (네스팅 : 객체안에 또 다른 객체를 포함시키는것)
Example.find({age : {$gt : 30}}); // age가 30초과인것을 조회
Example.find({age : {$lt : 30}}); // age가 30미만인것을 조회
AND연산자
여러조건을 모두 만족하는 결과만 필터하고 싶다면 하나의 객체 안에 여러 조건을 작성하면 된다.
Example.find({age: {$lt : 32}, email : {$regex : 'gmail\.com$'}});
.findOne()메소드
.find()메소드는 여러객체를 가져오는데 사용하고 .findById()는 _id에 해당하는 객체 하나를 가져오는데 사용한다. 만약, _id대신 다른조건을 만족하는 객체 하나를 가져오고 싶다면, .findOne()메소드를 사용하면 된다. 조건을 만족하는 객체가 여러개일 경우 처음으로 매칭된 객체 하나만 리턴한다.
Example.findOne({name : "James"}); // name이 James인 한 사람만 조회
'스키마'는 데이터의 틀이다. 이 스키마를 기반으로 데이터를 생성하고 조회하고 수정, 삭제할 수 있는 인터페이스를 '모델'이라고 한다. Mongoose로 어떤 데이터를 다루려면 항상 스키마와 모델을 가장 먼저 정의해야 한다.
# models/Task.js파일
import mongoose from 'mongoose';
const TaskSchema = new mongoose.Schema(
{
title : {
type : String,
required : true,
maxLength : 30,
},
description : {
type : String,
},
isComplete : {
type : Boolean,
required : true,
default : false,
}
},
{
timestamps : true
}
);
const Task = mongoose.model('Task', TaskSchema);
export default Task;
위와 같이, 필드 이름과 필드에 대한 정보를 객체 형태로 정의하면 된다.
자주 사용하는 필드 타입은 String, Number, Boolean, Date이고, default프로퍼티로 기본값을 설정할 수 있다. id필드는 정의하지 않아도 mongoose가 알아서 관리한다.
timestamps는 true옵션을 사용하면, createdAt와 updatedAt를 자동으로 생성, 관리 해준다.
유효성 검사에 대한 정보도 스키마에 같이 정의한다. 자주 사용하는 프로퍼티는 아래와 같다.
required : 데이터를 생성할 때 꼭 있어야 하는 필드
[문자열필드]
maxLength : 최대길이
minLength : 최소길이
enum : 특정 값 중 하나이어야 할때
예) enum : ['a', 'b']
match(특정패턴 이어야 할때)
[숫자형필드]
min : 최소
max : 최대
생성(CREATE)
.create()메소드를 사용해서 객체를 생성할 수 있다.app.post('/tasks', async(req, res) => { const newTask = req.body; const result = await Task.create(newTask); res.status(201).send(result); })
조회(READ)
여러객체를 조회할때는 .find()메소드를 사용한다.app.get('/tasks', async(req, res) => { const tasks = await Task.find(); res.send(tasks); });id로 특정 객체를 조회할때는 .findById()메소드를 사용한다.
app.get('/tasks/:id', async(req, res) => { const id = req.params.id; const task = await Task.findById(id); res.send(tasks); });id외에 다른 조건으로 필터를 하고 싶을때는 쿼리 필터를 사용하면 된다.
수정(UPDATE)
객체를 가져온 후 필드를 수정하고, .save()를 호출하면 된다.app.patch('tasks/:id', async (req, res) => { const task = await Task.findById(req.params.id); if(task){ Object.keys(req.body).forEach((key) => { task[key] = req.body[key]; }); await task.save(); res.send(task); }else{ res.status(404).send( { message : "해당하는 ID가 없습니다." } ); } })
삭제(DELETE)
.findByIdAndDelte()메소드를 이용해서 객체를 가져오는것과 동시에 삭제할 수 있다.app.delete('/tasks/:id', async(req, res) => { const id = req.params.id; const task = await Task.findByIdAndDelete(id); if(task){ res.status(204); }else { res.status(404).send( { message : "해당하는 ID가 없습니다." } ); } })'
비동기 코드에서 오류가 나면 서버가 죽기 떄문에 이를 따로 처리해야 한다.
모든 핸들러를 감싸는 asyncHandler()같은 함수를 정의하고 안에서 try, catch문을 활용할 수 있다.
function asyncHandler(handler){
return async function (req, res) => {
try {
handler(req, res);
}catch(e){
// error처리들...
}
}
}
app.get('/tasks', asyncHandler(async (req, res) => {
...
}));