Rest Api Tutorial

Build A Restful Api With Node.js Express & MongoDB

https://github.com/Juminhark/rest-tutorial

source code editor : Visual Studio Code

platform : Node.js

framework : Express

DB : MongoDB

module : nodemon, mongoose, dotenv

test tool : postman


Restful Api

REST(Representational state transfer)는 World Wide Web과 같은 분산 하이퍼미디어 시스템을 위한 소프트웨어 아키텍처의 한 형식이다.

엄격한 의미로 REST는 네트워크 아키텍처 원리의 모음이다. 여기서 '네트워크 아키텍처 원리'란 자원을 정의하고 자원에 대한 주소를 지정하는 방법 전반을 일컫는다.

간단한 의미로는, 웹 상의 자료를 HTTP위에서 SOAP이나 쿠키를 통한 세션 트랙킹 같은 별도의 전송 계층 없이 전송하기 위한 아주 간단한 인터페이스를 말한다.

REST 아키텍처 형식을 따르면 HTTP나 WWW이 아닌 아주 커다란 소프트웨어 시스템을 설계하는 것도 가능하다. 또한, 리모트 프로시저 콜 대신에 간단한 XML과 HTTP 인터페이스를 이용해 설계하는 것도 가능하다.

필딩의 REST 원리를 따르는 시스템은 종종 'RESTful'이란 용어로 지칭된다

REST 인터페이스의 원칙에 대한 가이드

  • 자원의 식별
  • 메세지를 통한 리소스의 조작
  • 자기서술적 메세지
  • 애플리케이션의 상태에 대한 엔진으로서 하이퍼 미디어

즉, HTTP 프로토콜을 의도에 맞게 정확히 활용하여 디자인하도록 유도하고 있기 때문에 디자인 기준이 명확해지며, 의미적인 범용성을 지니므로 중간 계층의 컴포넌트들이 서비스를 최적화하는 데 도움이 된다.

REST에서 가장 중요하며 기본적인 규칙은 아래 두 가지이다.

  • URI는 정보의 자원을 표현해야 한다.
  • 자원에 대한 행위는 HTTP Method로 표현한다.

HTTP Method 사용의 예는 아래와 같다.

Verb Action Path User for
GET index /books 모든 서적 리스트 조회
GET retrieve /books/:id 특정 서적 조회
POST create /books 신규 서적 생성
PUT replace /books/:id 특정 서적 갱신(없으면 생성)
PATCH update /books/:id 특정 서적 갱신
DELETE delete /books 모든 서적 삭제
DELETE delete /books/:id 특정 서적 삭제

npm 초기화

VSC(Visual Studio Code)실행후 local repository에 npm 초기화를 한다

>cd [local repository]

> npm init
	

초기화시 따로 값을 주지않으면 default값으로 pakage.json을 생성한다


Express, nodemon설치

Express는 웹 및 모바일 애플리케이션을 위한 일련의 강력한 기능을 제공하는 간결하고 유연한 Node.js 웹 애플리케이션 프레임워크이다.

nodemon은 프로젝트 폴더의 파일들을 모니터링하고 있다가 파일이 수정될 경우 자동으로 서버를 리스타트 시켜주는 도구이다

>npm install express nodemon
	

설치가 완료되면 package.json dependencies에 추가되었음을 알수있다


app.js 생성

app.js파일을 생성하고 아래와 같이 Express를 추가. Express는 자유롭게 활용할수 잇는 수많은 http 유틸리티 메소드 및 미들웨어를 통해 쉽고 빠르게 강력한 API를 작성할수 있다.

const express = require('express');
const app = express();
		
//ROUTES
app.get('/', (req, res) => {
	res.send('we are on home')
});
		
//How to we start listening to the server
app.listen(3000);
	

.get() 메소드를 통해 uri를 통한 request에대한 response를 대응할수 있고, .listen() 메소드를 통해 서버를 연결할수있다.

pakage.json에 start nodemon app.js를 추가

"scripts": {
	"start": "nodemon app.js"
}
	

server 실행

npm은 pakage.json을 실행하고 안에있는 scripts - start : nodemon app.js 으로 nodemon을 통해 app.js을 실행하게된다.

nodemon은 app.js을 모니터링하며 파일이 수정될때마다 서버를 restart한다

>npm start
	

server address를 통해 위에 생성된 app이 실행되는지 확인할수있다.


Route 추가

route를 추가함으로 app은 여러 uri에 따라 대응할수 있게된다. app.js에 다음을 추가하면

app.get('/posts', (req, res) => {
	res.send('we are on posts!!')
});
	

app.get() 메소드는 /posts 의 uri를 통해 들어오는 request에 response를 만들어 응답할수있다.


Middlewares 사용

.use()를 통해 특정 uri에 동작하는 Middleware를 만들수 있다. 아래 코드를 app.js에 추가해주고

app.use('/posts', () => {
	console.log('This is a Middleware running!');
});
	

해당 uri에 request를 보내면 Middleware가 동작함을 알수있다

현재의 Middleware 함수를 위와 같은 사용할때에는 요청 - 응답 주기가 종료되지 않는다.

이경우 next()를 호출하여 그 다음 미들웨어 함수에 제어를 전달해야 한다. 그렇지 않으면 해당 요청은 정지된 채로 방치된다.

app.use('/posts', (req, res, next) => {
	console.log('This is a middleware running!');
	next();
});
	

다시 uri로 request를 보내면 정상적으로 middleware가 작동하고

response도 방치되지않는다


mongoose 설치

mongoose는 Node.js와 MongoDB를 위한 ODM(Object Data Mapping) library이다.

>npm install mongoose
	

local repository에 mongoose를 설치하면 pakage.json dependencies에 추가된다


mongoDB Atlas

mongoDB Alas에 접속하여 계정생성후 Build a Cluster을 선택

Build a new Cluter

  • region : Singapore(free tier available)
  • Cluster Tier : M0 Sandbox
  • Cluster Name : [projectName]

cluster을 생성완료 후 app과 연결을 위해 Cluster의 connect 을선택

cluster을 사용할 user를 생성

Connet Your Application을 선택하여 connection string을 얻는다

app.js에 mongoose를 통해 DB에서 얻은 Connection String으로 DB을 연결한다.

const mongoose = require('mongoose');
	
//Connect To DB
mongoose.connect(
	'mongodb+srv://[userId]:[userpassword]@test-ljfri.mongodb.net/test?retryWrites=true',
	{ useNewUrlParser: true },
	() => {console.log('connected to DB!');}
);
	

이때 userid / userpassword 확인

다시 npm 을 start하면 DB가 연결되었음을 알수있다.

위와 같이 app.js에서 직접 DB접근경로를 노출하는경우 보안에 취약하다

이런경우 접근경로나 기타 환경변수를 따로 파일로 관리하며 app.js에서는 환경변수를 불러와 사용하도록하면 해결할수 있다


dotenv 설치

환경변수들을 .env 파일에서 로드하여 사용하기 위해 dotenv modlue을 설치

>npm install dotenv		
	

.env 파일 생성후 DB접근경로를 저장

DB_CONNECTION=mongodb+srv://admin:admin@test-ljfri.mongodb.net/test?retryWrites=true		
	

app.js에서 dotenv를 이용해 .env파일을 사용할수 있도록한다

require('dotenv/config');

//Connect To DB
mongoose.connect(
	process.env.DB_CONNECTION,
	{ useNewUrlParser: true },
	() => {console.log('connected to DB!');}
);		
	

router 따로 관리하기

route를 파일로 따로 모아주고 해당경로를 찾아와 사용하도록 하면 코드가 간결해진다

routes > posts.js 생성

const express = require('express');

const router = express.Router();
		
router.get('/',(req,res) => {
	res.send('We are on posts');
});
		
module.exports = router;		
	

app.js에서는 posts route를 import하여 사용

//Import Routes
const postsRoute = require('./routes/posts');
		
app.use('/posts', postsRoute);		
	

'/posts' uri는 postsRoute가 대응하며 postsRoute는 posts.js를 실행

address를 통해 확인해보면 작동확인

posts.js에 '/specific' 대응하는 .get()메서드 추가

router.get('/specific',(req,res) => {
	res.send('specific posts');
});
	

http://localhost:3000/posts/specific


정보 받기

client가 보내온 정보를 처리하는 과정을 test하기위해 test tool인 postman이 보내온 json을 app이 처리할수 있도록 한다

app 안에서 사용할 model을 생성한다

models > Post.js 생성하고

const mongoose = require('mongoose');

//schema : represent how that post look
const PostSchema = mongoose.Schema({
	title: {
		type: String,
		required: true
	},
	description:  {
		type: String,
		required: true
	},
	date:  {
		type: Date,
		default: Date.now
	}
});
		
module.exports = mongoose.model('Posts', PostSchema);
	

posts.js에서 model을사용할수 있도록 import한다

const Post = require('../models/Post');

//client가 보내온 request
router.post('/', (req,res) => {
	console.log(req.body);
});
	

postman

postman : url로 다양한 request를 보내볼수 있고 그에따른 response도 확인할수 있어 쉽게 rest api을 테스트할수 있는 도구이다

http://localhost:3000/posts에 GET형식으로 request를 보내면 다음과 같다

Body > raw > JSON(application/json)으로 다음내용을 담아 post형식으로 보낸다

{
	"title": "my first post",
	"description" : "This is my descrition of my love life"
}
	

아직 보내온 reqeust에 대한 response는 없지만 reqeust를 sever에서 받았다는것은 알수있다

posts.js > console.log(req.body);의 body에서 해당 내용을 표현하지 못하기 때문에 undifined가 출력된다. 이를 해결하기위해 body-parser를 설치한다

>npm install body-parser
	

app.js에 body-parser를 import

//Body-Parser
const bodyParser = require('body-parser');
app.use(bodyParser.json());
	

다시 npm start 하고 post request를 보내보면 다음을 얻을수 있다


DB에 저장하기

postman이 '/posts'로 body에 json 정보를 담아 request를 보내면

app.js가 request를 받아

const postsRoute = require('./routes/posts');
app.use('/posts', postsRoute); 
	

위 코드를 통해 posts.js로 post방식 request를 넘기고

post.js 에서는 받아온 post방식의 body를 model:post객체를 만들어 json을 담는다.

router.post('/', async (req,res) => {
    const post = new Post({
        title: req.body.title,
        description: req.body.description
    });

    try{
    const savedPost = await post.save();
    res.json(savedPost);
    }catch(err){
        res.json({messge:err});
    }
});
	

생성된 객체 post는 .save()메서드를 통해 json형태로 DB로 넘겨 저장한다

postman으로 test해보면 request에 대한 response가 응답되고

DB에 접속해 보면 데이터가 저장이 된것을 확인할수 있다.


get 방식의 uri 사용방법

router.get('/:postId', (req,res) =>{
	console.log(req.params.postId);
});
	

'/posts/:postId' 으로 들어온 쿼리 스트링을 :postId 로 받아오고 있다

: 가 붙은 postId에 쿼리스트링을 저장한다

DB 정보 가져오기

posts.js에 다음을 코드를 추가

router.get('/',async (req,res) => {
	try{
		const posts = await Post.find();
		res.json(posts);
	}catch(err){
		res.json({message:err});
	}
});
	

'/posts' uri에 get방식으로 request를 보내면 Post DB정보를 가져와 response로 응답한다

postman으로 test해보면 다음과 같다


특정 DB 정보 가져오기

posts.js에 다음을 코드를 추가

router.get('/:postId',async (req,res) =>{
    try{
        const post = await Post.findById(req.params.postId);
        res.json(post);
    }catch(err){
        res.json({message:err});
    }
});
	

'/posts/:id' 쿼리스트링으로 들어온 id를 postId로 저장하고

Post.findById(req.params.postId) 를 통해 특정 id를 통해 검색하여

post 를 찾아 post에 저장하여 response json으로 보내준다

가지고 있는 post중 특정 post의 id를찾아

해당 id를 가지고 있는 특정 post를 response 응답해주는것을 알수있다.


특정 post delete

특정 post 가져오는 방식과 유사하다

router.delete('/:postId',async (req,res) =>{
	try{
		const removedPost = await Post.remove({_id: req.params.postId});
		res.json(removedPost);
	}catch(err){
		res.json({message:err});
	}
});
	

postman에서 delete 방식으로 posts/:postId 을 보내면

_id : req.params.postId 로 _id에 postId저장.

_id로 찾은 post를 .remove 메소드로 삭제한다


특정 post의 특정 항목 update

router.patch('/:postId',async (req,res) =>{
    try{
        const updatePost = await Post.updateOne(
            {_id: req.params.postId}, 
            { $set: {title: req.body.title}}
        );

        res.json(updatePost);
    }catch(err){
        res.json({message:err});
    }
});
	

update할 post의 id를 확인후

postman에서 patch 방식으로 posts/:postId 에 아래 json을 보내면

{
	"title": "We are learning about backend stuff"
};
	

postId와 보내온 json을 .updateOne()메소드로 특정 post의 특정 항목을 update 한다


Reference