Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
Tags
- builder
- factory
- 일급 컬렉션
- 일급 객체
- Spring
- Dependency Injection
- lombok
- Volatile
- spring security
- Google OAuth
- OAuth 2.0
- synchronized
- java
Archives
- Today
- Total
HJW's IT Blog
[CodeIt] 07월 2주차 본문
필요한 데이터 관리 + 프론트엔드에 전송 → 이를 백엔드 서버라 부른다
HTTP 메소드 + URL = 엔드포인트
엔드포인트의 집함 = API
REST API
NodeJS → 웹 브라우저 바깥에서 JS 를 사용할 수 있게 해주는 프레임워크
EXPRESS → JS 백엔드 개발에서 가장 유명한 라이브러리
- Req 과 res 를 쉽게 다룰 수 있음
- 특정 구조를 고집하지 않고 최소한의 기능만 제공
MongoDB → 데이터를 테이블이 아닌 문서의 형태로 저장
- 문서 하나 = document
- 문서의 모음 = collection
예제 (ToDo) API 문서
엔드포인트
GET /tasks
GET /tasks/:id
POST /tasks
DELETE /tasks/:id
PATCH /tasks/:id
TASK 객체
- 속성
- _id: string (Task 객체의 고유 식별자)
- title: string (Task 제목, 최대 30자)
- description : string (테스크 상세설명 , optional)
- isComplete : boolean (테스크 완료 여부, 자동생성 필드)
- createdAt : string (테스크 생성 시점 타임스템프)
- updatedAt : string (테스크 수정 시점 타임스템프)
- 객체 예시
{
"_id": "641bf3c9c0a964b65289902a",
"title": "파이썬 공부",
"description": "프로그래밍 시작하기 in Python 토픽 끝내기",
"isComplete": false,
"createdAt": "2023-03-23T06:34:11.617Z",
"updatedAt": "2023-03-23T06:34:11.617Z"
}
TASK 목록 조회
요청
GET /tasks
반환
200 OK
Content-Type: application/json
[
{
"_id": "641bf3c9c0a964b65289902a",
"title": "파이썬 공부",
"description": "프로그래밍 시작하기 in Python 토픽 끝내기",
"isComplete": false,
"createdAt": "2023-03-23T06:34:11.617Z",
"updatedAt": "2023-03-23T06:34:11.617Z"
},
{
"_id": "641bf3c9c0a964b65289903c",
"title": "독서",
"description": "30 페이지",
"isComplete": false,
"createdAt": "2023-03-23T06:34:10.617Z",
"updatedAt": "2023-03-23T06:34:10.617Z"
},
...
]
TASK 객체 전체 목록을 최신순으로 반환
TASK 조회
요청
GET /tasks/:id
GET <http://localhost:3000/tasks/641bf3c9c0a964b65289902a>
반환
200 OK
Content-Type: application/json
{
"_id": "641bf3c9c0a964b65289902a",
"title": "파이썬 공부",
"description": "프로그래밍 시작하기 in Python 토픽 끝내기",
"isComplete": false,
"createdAt": "2023-03-23T06:34:11.617Z",
"updatedAt": "2023-03-23T06:34:11.617Z"
}
TASK 생성
// 요청
POST /tasks
POST <http://localhost:3000/tasks>
Content-Type: application/json
{
"title": "강아지 산책",
"description": "강아지랑 30분 산책하기"
}
// 반환
201 Created
Content-Type: application/json
{
"_id": "641bf3c9c0a964b65289902d",
"title": "강아지 산책",
"description": "강아지랑 30분 산책하기",
"isComplete": false,
"createdAt": "2023-04-17T05:41:35.319Z",
"updatedAt": "2023-04-17T05:41:35.319Z"
}
TASK 수정
// 요청
PATCH /tasks/:id
PATCH <http://localhost:3000/tasks/641bf3c9c0a964b65289902a>
Content-Type: application/json
{
"title": "자바스크립트 공부",
"description": "프로그래밍 시작하기 in JavaScript 토픽 끝내기",
}
// 반환
200 OK
Content-Type: application/json
{
"_id": "641bf3c9c0a964b65289902a",
"title": "자바스크립트 공부",
"description": "프로그래밍 시작하기 in JavaScript 토픽 끝내기",
"isComplete": false,
"createdAt": "2023-03-23T06:34:11.617Z",
"updatedAt": "2023-03-24T06:34:11.617Z"
}
TASK 삭제
// 요청
DELETE /tasks/:id
// 예시
DELETE <http://localhost:3000/tasks/641bf3c9c0a964b65289902a>
// 반환
204 No Content
EXPRESS 로 API 서버 (환경설정)
- nodejs 설치
npm install express
npm install --save-dev nodemon
- package.json 에 다음 추가
- "type": "module”
시작하기
import express from 'express';
const app = express();
app 을 통해 이제 라우트를 생성할 수 있다
app.get('/hello', (req,res) => {});
url 경로 + 콜백함수 의 구조이다
- request handler 라고도 불린다
- req handler 는 두개의 파라미터를 가지는데, request 와 response 이다
app.listen(3000, () => console.log('Server Started'));
- 3000 은 port number 이며, 에플리케이션이 실행되면, 콜백함수가 실행된다.
쿼리스트링
URL 에서 ? 뒤에 오는 부분
예시
<http://localhost:3000/tasks?sort=oldest&count=3>
app.get('/tasks', (req,res) => {
const sort = req.query.sort;
const count = Number(req.query.count);
const compareFn =
sort === 'oldest'
? (a, b) => a.createdAt - b.createdAt
: (a, b) => b.createdAt - a.createdAt;
let newTasks = tasks.sort(compareFn);
if(count){
newTasks = newTasks.slice(0, count);
}
res.send(newTasks);
});
- 쿼리파라미터를 받아온 뒤 compareFn 을 통해 정렬
- count를 받아와 slicing 후 해당 객체 반환
Dynamic URL
- 일정하지 않고 일부가 변화하는 url
GET <http://localhost:3000/tasks/2>
app.get('/tasks/:id', (req, res)=> {
const id = Number(req.params.id);
const task = tasks.find((task) => task.id === id);
if(task){
res.send(task);
}else{
res.status(404).send({message: 'cannot find given id.'});
}
});
바뀌는 부분을 : 을 사용하여 나타낸다
req 의 params 에 저장
POST REQUEST
app.use(express.json());
- 이를 통해 파싱을 해주어야 한다
- req 의 type 이 application/json이라면 body 를 파싱한다는 뜻
PATCH REQUEST
app.patch('/tasks/:id', (req, res)=> {
const id = Number(req.params.id);
const task = tasks.find((task) => task.id === id);
if(task){
Object.keys(req.body).forEach((key)=>{
task[key] = req.body[key];
});
task.updatedAt = new Date();
res.send(task);
}else{
res.status(404).send({message: 'cannot find given id.'});
}
});
DELETE REQUEST
app.delete('/tasks/:id', (req, res)=> {
const id = Number(req.params.id);
const idx = tasks.findIndex((task) => task.id === id);
if(idx >= 0){
tasks.splice(idx, 1);
res.sendStatus(204);
}else{
res.status(404).send({message: 'cannot find given id.'});
}
});
Mongoose
- schema 를 생성해야 한다
//schema 생성 코드
import mongoose from "mongoose";
const TaskSchema = new mongoose.Schema(
{
title: {
type: String,
},
description: {
type: String,
},
isComplete: {
type: Boolean,
default: false,
},
},
{
timestamps: true,
}
)
const Task = mongoose.model('Task', TaskSchema);
export default Task;
- timestamps 사용시 mongoose 가 자동으로 createdAt, updated At 생성
- model 은 schema를 기반으로, 데이터를 수정, 삽입, 삭제, 조회할수 있는 interface
- 'Task' 는 tasks 라는 collection 의 데이터를 다룬다
- seed data → 초기 데이터
- seeding → 초기데이터 삽입 과정
// package.json
"seed": "node data/seed.js" 추가
// seed.js
import mongoose from "mongoose";
import data from './mock.js'
import Task from '../models/Task.js'
import { DATABASE_URL } from "../env.js";
mongoose.connect(DATABASE_URL);
await Task.deleteMany({});
await Task.insertMany(data);
mongoose.connection.close();
이후, 터미널에서 npm run seed
// mongoose 를 이용한 get task
app.get('/tasks', async (req,res) => {
const sort = req.query.sort;
const count = Number(req.query.count) || 0;
const sortOption = {
createdAt: sort === 'oldest' ? 'asc' : 'desc'
};
const tasks = await Task.find().sort(sortOption).limit(count); // 모든 task 객체를 가져온다, sortOption으로 정렬, count 개 만큼
res.send(tasks);
});
Query Filter
// 필드의 값이 특정값과 일치하는지 확인
Person.find({ name: 'James' });
//필드가 특정값 초과 : $gt, 특정 값 미만 : $lt
Person.find({ age: { $gt: 35 } });
// 문자열 필드가 특정 패턴을 가지는지
Person.find({ email: { $regex: 'gmail\\.com$' } });
데이터 생성 및 유효성 검사
// 데이터 생성
app.post('/tasks', async (req, res) =>{
const newTask = await Task.create(req.body);
res.status(201).send(newTask);
});
// 만약 필수로 필요로 하는 필드가 있다면
// 스키마에 다음과 같이 옵션을 주면 된다
// validate 는 제목이 최소 두 단어인지 검증하는 함수이다
title: {
type: String,
required: true,
maxLength: 30,
validate: {
validator: function (title){
return title.split(' '.length) > 1;
},
message: 'Must Contain At Least 2 Words.',
}
},
<aside> 💡 mongoose 는 스키마에 정의되지 않은 필드는 무시한다
</aside>
- 잘못된 데이터로 validation 오류가 생기면 서버가 죽는다
- 다음과 같이 비동기 핸들러를 구현하여 해결할 수 있다
function asyncHandler(handler){
return async function (req, res){
try{
await handler(req, res);
} catch(e){
if(e.name === 'ValidationError'){
res.status(400).send({message: e.message});
}else if(e.name === 'CastError'){
res.status(404).send({message: 'Cannot find given id.'});
}else{
res.status(500).send({message: e.message});
}
}
}
}
app.post('/tasks', asyncHandler(async (req, res) =>{
const newTask = await Task.create(req.body);
res.status(201).send(newTask);
}));
- 데이터 수정 및 삭제
app.patch('/tasks/:id', async (req, res)=> {
const id = req.params.id;
const task = await Task.findById(id);
if(task){
Object.keys(req.body).forEach((key)=>{
task[key] = req.body[key];
});
res.send(task);
}else{
res.status(404).send({message: 'cannot find given id.'});
}
});
app.delete('/tasks/:id', async (req, res)=> {
const id = req.params.id;
const task = await Task.findByIdAndDelete(id);
if(task){
res.sendStatus(204);
}else{
res.status(404).send({message: 'cannot find given id.'});
}
});
\
환경 변수
- 프로그램이 실행되는 환경과 관련이 있는 변수들
- ex) DATABASE_URL
dotenv 로 환경변수 다루기
npm install dotenv
.env 파일 생성
DATABASE_URL="mongodb+srv://<username>:<password>@todo-api.l0aepsl.mongodb.net/todo-api?retryWrites=true&w=majority"
PORT=3000
// app.js
import * as dotenv from 'dotenv';
dotenv.config();
// ...
mongoose.connect(process.env.DATABASE_URL).then(() => console.log('Connected to DB'));
// ...
app.listen(process.env.PORT || 3000, () => console.log('Server Started'));
// seed.js
// ...
import * as dotenv from 'dotenv';
dotenv.config();
// ...
mongoose.connect(process.env.DATABASE_URL);
- CORS 오류
- 현 상태로 배포하게 되면 프론트엔드에서 api 사용 불가, cors error 가 뜬다
npm install cors
// ...
import cors from 'cors';
// ...
const app = express();
app.use(cors());
app.use(express.json());
- 특정 주소만 cors 배포를 허용할 수 도 있다
const app = express();
const corsOptions = {
origin: ['', '<https://my-todo.com>'],
};
app.use(cors(corsOptions));
app.use(express.json());