7 기타 library 소개
source: categories/study/react_restapi_graphql/react_restapi_graphql8.md
7.1 LowDB
7.1.1 REST API - LowDB
이번에는 번외로 json DB
를 이용하는 서버를 좀 더 사용하기 쉽게해주는 LowDB
라고하는 라이브러리를 적용해보겠습니다.
우선 REST API
에 대해 먼저 할건데요,
REST API
는 3장에서 다뤘던 내용들로 서버쪽만 다루도록 하겠습니다.
cd server/
yarn add lowdb
// server/src/dbController.js
// file system, path의 resolve는 lowdb가 알아서해주므로 삭제합시다.
- import fs from 'fs';
- import {resolve} from 'path';
+ import {LowSync, JSONFileSync} from "lowdb";
// 경로를 ./src/db.json으로 설정. 모두 한곳에 뭉칠 겁니다.
// 현재 server/src/db 폴더에 messages.json, users.json이 있는데, 이를 server/src/db.json 파일로 한데 묶을려고 계획중입니다.
// server/src/db.json이라는 하나의 파일 안에서 messages, users가 모두 있도록 하겠습니다.
+ const adapter = new JSONFileSync('./src/db.json');
// 그리고 db라는 걸 만들겁니다.
+ const db = new LowSync(adapter);
// db에서 이제 write, read라는 명령을 쓰게 될겁니다.
+ export default db;
db.json
은 아래와같이 만듭니다.
이러면 DB가 만들어진겁니다.
// server/src/db.json
{
"messages": [{"id":"2e0c7e5e-7745-4b89-ae66-75ac67ec7839","text":"로이라는 닉네임으로 새글이 등록됩니다.","userId":"roy","timestamp":1631433146774},{"id":"398a4852-0d87-43e0-ae9d-d681d1dc2297","text":"제이라는 닉네임으로 새글이 등록됩니다.","userId":"jay","timestamp":1631433059221},{"id":"ae7ab8a5-8865-4a9f-9ab8-716eef2e9ec1","text":"새글 작성 id는 어떻게 요청가는지 봅시다.","userId":"jay","timestamp":1631288042010},{"id":"7e0ae0c5-048c-4902-af6b-ad324934c5ca","text":"새글 roy입니다.","userId":"roy","timestamp":1631261337522},{"id":"861d5b1e-596e-4596-8fd6-e5b4fadb07d1","text":"새글 jay입니다. 수정했습니다. 또 수정했습니다.","userId":"jay","timestamp":1631261326171},{"id":"9fe95b38-9f9b-4cb5-8336-941107b38a01","text":"새글입니다","userId":"roy","timestamp":1631261286845},{"id":"033c3344-e03f-40fa-8038-66fdec7d9bb5","text":"새글입니다","userId":"roy","timestamp":1631261275381},{"id":"a4653af2-808e-4751-8777-940c8f70fc46","text":"새글입니다.","userId":"roy","timestamp":1631261265660},{"id":"50","userId":"jay","timestamp":1234570890123,"text":"50 mock text"},{"id":"49","userId":"jay","timestamp":1234570830123,"text":"49 mock text"},{"id":"48","userId":"jay","timestamp":1234570770123,"text":"48 mock text id는 왜 이상하게 바뀔까"},{"id":"47","userId":"jay","timestamp":1234570710123,"text":"47 mock text"},{"id":"46","userId":"jay","timestamp":1234570650123,"text":"46 mock text"},{"id":"45","userId":"roy","timestamp":1234570590123,"text":"45 mock text"},{"id":"44","userId":"roy","timestamp":1234570530123,"text":"44 mock text"},{"id":"43","userId":"jay","timestamp":1234570470123,"text":"43 mock text"},{"id":"42","userId":"jay","timestamp":1234570410123,"text":"42 mock text"},{"id":"41","userId":"jay","timestamp":1234570350123,"text":"41 mock text"},{"id":"40","userId":"roy","timestamp":1234570290123,"text":"40 mock text"},{"id":"39","userId":"jay","timestamp":1234570230123,"text":"39 mock text"},{"id":"38","userId":"roy","timestamp":1234570170123,"text":"38 mock text"},{"id":"37","userId":"roy","timestamp":1234570110123,"text":"37 mock text"},{"id":"36","userId":"jay","timestamp":1234570050123,"text":"36 mock text"},{"id":"35","userId":"roy","timestamp":1234569990123,"text":"35 mock text"},{"id":"34","userId":"roy","timestamp":1234569930123,"text":"34 mock text"},{"id":"33","userId":"roy","timestamp":1234569870123,"text":"33 mock text"},{"id":"32","userId":"jay","timestamp":1234569810123,"text":"32 mock text"},{"id":"31","userId":"jay","timestamp":1234569750123,"text":"31 mock text"},{"id":"30","userId":"roy","timestamp":1234569690123,"text":"30 mock text"},{"id":"29","userId":"roy","timestamp":1234569630123,"text":"29 mock text"},{"id":"28","userId":"roy","timestamp":1234569570123,"text":"28 mock text"},{"id":"27","userId":"jay","timestamp":1234569510123,"text":"27 mock text"},{"id":"26","userId":"roy","timestamp":1234569450123,"text":"26 mock text"},{"id":"25","userId":"jay","timestamp":1234569390123,"text":"25 mock text"},{"id":"24","userId":"roy","timestamp":1234569330123,"text":"24 mock text"},{"id":"23","userId":"jay","timestamp":1234569270123,"text":"23 mock text"},{"id":"22","userId":"roy","timestamp":1234569210123,"text":"22 mock text"},{"id":"21","userId":"roy","timestamp":1234569150123,"text":"21 mock text"},{"id":"20","userId":"roy","timestamp":1234569090123,"text":"20 mock text"},{"id":"19","userId":"roy","timestamp":1234569030123,"text":"19 mock text"},{"id":"18","userId":"roy","timestamp":1234568970123,"text":"18 mock text"},{"id":"17","userId":"roy","timestamp":1234568910123,"text":"17 mock text"},{"id":"16","userId":"roy","timestamp":1234568850123,"text":"16 mock text"},{"id":"15","userId":"jay","timestamp":1234568790123,"text":"15 mock text"},{"id":"14","userId":"roy","timestamp":1234568730123,"text":"14 mock text"},{"id":"13","userId":"jay","timestamp":1234568670123,"text":"13 mock text"},{"id":"12","userId":"roy","timestamp":1234568610123,"text":"12 mock text"},{"id":"11","userId":"jay","timestamp":1234568550123,"text":"11 mock text"},{"id":"10","userId":"jay","timestamp":1234568490123,"text":"10 mock text"},{"id":"9","userId":"roy","timestamp":1234568430123,"text":"9 mock text"},{"id":"8","userId":"roy","timestamp":1234568370123,"text":"8 mock text"},{"id":"7","userId":"roy","timestamp":1234568310123,"text":"7 mock text"},{"id":"6","userId":"jay","timestamp":1234568250123,"text":"6 mock text"},{"id":"5","userId":"roy","timestamp":1234568190123,"text":"5 mock text"},{"id":"4","userId":"jay","timestamp":1234568130123,"text":"4 mock text"},{"id":"3","userId":"roy","timestamp":1234568070123,"text":"3 mock text"},{"id":"2","userId":"jay","timestamp":1234568010123,"text":"2 mock text"},{"id":"1","userId":"roy","timestamp":1234567950123,"text":"1 mock text"}],
"user": {
"roy": {"id": "roy", "nickname": "로이"},
"jay": {"id": "jay", "nickname": "제이"}
}
}
기존 server/src/db/
폴더는 삭제해줍니다.
이젠 routes
쪽만 수정을하면 됩니다.
// server/src/routes/messages.js
import {v4} from "uuid";
// writeDB, readDB 필요없이 그냥 db를 불러오겠습니다.
- import {readDB, writeDB} from "../dbController.js";
+ import db from "../dbController.js";
- const getMsgs = () => readDB('messages');
+ const getMsgs = () => {
+ // lowdb에서 제공하는 기능입니다. db.read(); 메소드는 db 자체를 읽어오는 기능을 수행합니다.
+ db.read();
+ // 이 db는 계속 캐시에 남아있는 상태여서, 만약에 db.data가 있으면 db.data를 계속해서 사용하고
+ // 없을 경우에는 messages를 다시 빈 배열로 만들어주는 안전장치를 마련합니다.
+ db.data = db.data || {messages: []};
+ return db.data.messages;
+ }
- const setMsgs = data => writeDB('messages', data);
const messagesRoute = [
{ // GET MESSAGES : 전체 메시지를 가져오는 명령
method: 'get',
route: '/messages',
handler: ({query: {cursor = ''}}, res) => {
const msgs = getMsgs();
const fromIndex = msgs.findIndex(msg => msg.id === cursor) + 1
res.send(msgs.slice(fromIndex, fromIndex + 15))
}
},
{ // GET MESSAGE : id 하나에 대한 메시지를 가져오는 것도 살펴봅시다.
method: 'get',
route: '/messages/:id',
handler: ({params: {id}, res}) => {
try {
const msgs = getMsgs();
const msg = msgs.find(m => m.id === id);
if (!msg) throw Error('not found')
res.send(msg);
} catch (err) {
res.status(404).send({ error: err })
}
}
},
{ // CREATE MESSAGE
method: 'post',
route: '/messages',
handler: ({body}, res) => {
try {
if (!body.userId) throw Error('no userId');
const msgs = getMsgs();
const newMsg = {
id: v4(),
text: body.text,
userId: body.userId,
timestamp: Date.now(),
}
- msgs.unshift(newMsg)
- setMsgs(msgs);
// db.write() 코드로 db에 쓸 수 있다.
+ db.data.messages.unshift(newMsg);
+ db.write();
res.send(newMsg);
} catch (err) {
res.status(500).send({error: err})
}
}
},
{ // UPDATE MESSAGE
method: 'put',
route: '/messages/:id',
handler: ({body, params: {id}}, res) => {
try {
const msgs = getMsgs();
const targetIndex = msgs.findIndex(msg => msg.id === id);
if (targetIndex < 0) throw '메시지가 없습니다.'
if (msgs[targetIndex].userId !== body.userId) throw '사용자가 다릅니다.'
const newMsg = {
...msgs[targetIndex],
text: body.text,
}
- msgs.splice(targetIndex, 1, newMsg);
- setMsgs(msgs);
+ db.data.messages.splice(targetIndex, 1, newMsg);
+ db.write();
res.send(newMsg);
} catch (err) {
res.status(500).send({error: err})
}
}
},
{ // DELETE MESSAGE
method: 'delete',
route: '/messages/:id',
handler: ({params: {id}, query: {userId}}, res) => {
try {
const msgs = getMsgs();
const targetIndex = msgs.findIndex(msg => msg.id === id)
if (targetIndex < 0) throw '메시지가 없습니다.'
if (msgs[targetIndex].userId !== userId) throw '사용자가 다릅니다.'
- msgs.splice(targetIndex, 1);
- setMsgs(msgs);
+ db.data.messages.splice(targetIndex, 1);
+ db.write();
res.send(id);
} catch (err) {
res.status(500).send({error: err});
}
}
},
]
export default messagesRoute
음.. REST API 여기 DELETE에서 이 setMsgs(msgs); 코드가 빠져있었다. (위에선 그냥 넣어둠)
저 부분을 강의에서도 빼먹은건가 아니면 내가 빼먹은건가..
원래 상황상 setMsgs(msgs);는 무조건 있었어야한게 맞는거같긴한데..
삭제했으니까 DB에 새로 써야지. 안그래? 내 생각은 그런데 왜 안썼었지..
내가 빠트린건가.. 아니면 강의에서도 실수로 빼먹었었나..
여튼 위와 같이 수정하면된다.
server/src/routes/users.js
도 간단하게 보고가도록 하겠습니다.
users.js
에서는 아래와 같이만 바꾸면됩니다.
users.js
에서는 GET
만 넣었지 나머지 새로쓰고, 수정하는 것은 넣지 않았기 때문에 이렇게만 수정해주면 되겠습니다.
// server/src/routes/users.js
- import {readDB} from "../dbController.js";
+ import db from "../dbController.js";
- const getUsers = () => readDB('users')
+ const getUsers = () => {
+ db.read();
// db.data에 아무것도 없을 경우를 대비한 안전장치
+ db.data = db.data || {users: {}};
+ return db.data.users;
+ }
const usersRoute = [
{
method: 'get',
route: '/users',
handler: (req, res) => {
const users = getUsers();
res.send(users)
}
},
{
method: 'get',
route: '/users/:id',
handler: ({params: {id}}, res) => {
try {
const users = getUsers()
const user = users[id]
if (!user) throw Error('사용자가 없습니다.')
res.send(user)
} catch (err) {
res.status(500).send({errer: err})
}
}
}
]
export default usersRoute
이 상태에서 nodemon
에서 감시하는 대상이 바뀌었죠?
// server/nodemon.json
{
"watch": ["src"],
- "ignore": ["db/**/*"],
+ "ignore": ["db.json"],
"env": {
"NODE_ENV": "development"
}
}
root
폴더로가서 yarn run server
, yarn run client
명령어를 실행합니다.
cd ../
yarn run server
yarn run client
위와 같이 똑같이 작동하는 것을 보실 수 있습니다.
새 글이 잘 등록되는 것을 볼 수 있습니다.
그럼 위와 같이 db.json
에도 새글 등록한 것이 저장되는 것을 보실 수 있습니다.
이렇게 수정을하면
이렇게 db.json
에 바로 반영이되는 것을 볼 수 있습니다.
이게 다 입니다.
바뀐게 거의 없거니와 기존에쓰던 dbController.js
자체도 굉장히 심플했는데,
그것보다도 더 심플하게 해주는 것 외엔 특별한 것은 없습니다.
그밖에 사용법에 대해선 특이한게 없어서 REST API
관련 소개는 여기까지 하겠습니다.
7.1.2 GraphQL - LowDB
GraphQL
도 안어렵기 때문에 바로 적용을 해보겠습니다.
GraphQL
도 마찬가지로 똑같습니다.
cd server/
yarn add lowdb
설치가 끝났으면 server/nodemon.json
에서
// server/nodemon.json
{
"watch": ["src"],
- "ignore": ["db/**/*"],
+ "ignore": ["db.json"],
"env": {
"NODE_ENV": "development"
}
}
그 다음에 server/src/dbController.js
에서
// server/src/dbController.js
import {LowSync, JSONFileSync} from "lowdb";
const adapter = new JSONFileSync('./src/db.json');
const db = new LowSync(adapter);
export default db;
그 다음에 server/src/index.js
파일을 수정합시다.
// server/src/index.js
import express from 'express'
import {ApolloServer} from "apollo-server-express";
import resolvers from './resolvers/index.js'
import schema from './schema/index.js'
- import {readDB} from "./dbController.js";
+ import db from "./dbController.js";
// 이 상태에서 readDB 라는 함수를 다시 만들겠습니다.
// 아래와 같이 작성하면 return하는 db.data 안에는 messages와 users가 모두 들어있는 상태가되겠죠?
+ const readDB = () => {
+ db.read();
+ return db.data;
+ }
const server = new ApolloServer({
typeDefs: schema,
resolvers,
context: {
// 아래 부분이 아래와 같이 수정되면 되겠습니다.
// 그런데 아래 db와 위에 import해온 db와 이름이 겹쳐서 resolvers에서 문제가될겁니다.
// 그래서 아래를 models라는 이름으로 바꿉니다.
- db: {
- messages: readDB('messages'),
- users: readDB('users'),
- }
+ models: readDB(),
}
});
const app = express();
await server.start();
server.applyMiddleware({
app,
path: '/graphql',
cors: {
origin: [
'http://localhost:3000',
'https://studio.apollographql.com'
],
credentials: true,
}
})
await app.listen({port: 8000})
console.log('server listening on 8000...')
위와 같이 수정한 상태에서 resolvers/messages.js
로 가면 이전에 db
로 되어있던 부분들을 전부 models
로 바꾼 다음에, 아래와 같이 수정합니다.
// server/src/resolvers/messages.js
import {v4} from "uuid";
- import {writeDB} from "../dbController.js";
+ import db from "../dbController.js";
// setMsgs라는 명령어는 필요없으니 지웁니다.
- const setMsgs = data => writeDB('messages', data)
/**
* parent: 대부분 사용되지 않는 root query type의 이전 객체, parent 객체라고 하고, 거의 사용되지 않습니다.
* args: graphQL Query에 필요한 필드에 제공되는 인수(parameter)가 들어있는 객체
* context: 로그인한 사용자 정보, DB Access 권한 등의 중요한 정보들을 담고있는 객체
* 위 각각에 어떤 내용이 들어가있는지는 나중에 따로 살펴보도록 하겠습니다.
* */
const messageResolver = {
Query: {
- messages: (parent, {cursor = ''}, {db}) => {
+ messages: (parent, {cursor = ''}, {models}) => {
- const fromIndex = db.messages.findIndex(msg => msg.id === cursor) + 1;
+ const fromIndex = models.messages.findIndex(msg => msg.id === cursor) + 1;
- return db.messages?.slice(fromIndex, fromIndex + 15) || [];
+ return models.messages?.slice(fromIndex, fromIndex + 15) || [];
},
- message: (parent, {id = ''}, {db}) => {
+ message: (parent, {id = ''}, {models}) => {
- return db.messages.find(msg => msg.id === id)
+ return models.messages.find(msg => msg.id === id)
},
},
Mutation: {
- createMessage: (parent, {text, userId}, {db}) => {
+ createMessage: (parent, {text, userId}, {models}) => {
const newMsg = {
id: v4(),
text,
userId,
timestamp: Date.now(),
}
- db.messages.unshift(newMsg);
+ models.messages.unshift(newMsg);
- setMsgs(db.messages);
// GraphQL의 장점은 models로 불러온 것이 db랑 계속 연동이되고 있다라는 부분입니다.
// 그래서 data가 변경이되었을때 그 models 그대로 db를 쓰게끔하는 db.write() 명령어만 수행해주면됩니다.
+ db.write();
return newMsg;
},
- updateMessage: (parent, {id, text, userId}, {db}) => {
+ updateMessage: (parent, {id, text, userId}, {models}) => {
- const targetIndex = db.messages.findIndex(msg => msg.id === id)
+ const targetIndex = models.messages.findIndex(msg => msg.id === id)
if (targetIndex < 0) throw Error('메시지가 없습니다.');
- if (db.messages[targetIndex].userId !== userId) throw Error('사용자가 다릅니다.');
+ if (models.messages[targetIndex].userId !== userId) throw Error('사용자가 다릅니다.');
const newMsg = {
- ...db.messages[targetIndex],
+ ...models.messages[targetIndex],
text,
}
- db.messages.splice(targetIndex, 1, newMsg)
+ models.messages.splice(targetIndex, 1, newMsg)
- setMsgs(db.messages)
+ db.write();
return newMsg;
},
- deleteMessage: (parent, {id, userId}, {db}) => {
+ deleteMessage: (parent, {id, userId}, {models}) => {
- const targetIndex = db.messages.findIndex(msg => msg.id === id);
+ const targetIndex = models.messages.findIndex(msg => msg.id === id);
if (targetIndex < 0) throw '메시지가 없습니다.';
- if (db.messages[targetIndex].userId !== userId) throw '사용자가 다릅니다.';
+ if (models.messages[targetIndex].userId !== userId) throw '사용자가 다릅니다.';
- db.messages.splice(targetIndex, 1);
+ models.messages.splice(targetIndex, 1);
- setMsgs(db.messages)
+ db.write();
return id;
},
},
Message: {
- user: (msg, args, {db}) => db.users[msg.userId]
+ user: (msg, args, {models}) => models.users[msg.userId]
},
}
export default messageResolver
여기도 delete
부분에 setMsgs(db.messages)
가 작성이 안되어있었네..
흠.. 원래 들어가는게 맞는데 내가 빼먹었었나보다..
db
를 아직 안만들었죠? db
를 만들겠습니다.
server/src/db.json
아래와 같이 만들고 server/src/db/
폴더는 지우겠습니다.
{
"messages": [{"id":"d679cdd2-1fd7-4986-84e4-bbe5a7712819","text":"sdfsdfsdf","userId":"roy","timestamp":1632200478412},{"id":"8fd5649e-3e73-4ed2-94cb-8bbaebc2473e","text":"안녕하세요 추가한 메시지입니다.","userId":"roy","timestamp":1632186383971},{"id":"5dddf235-2171-4d9b-855e-b94a8bce7c89","text":"ㄴㅇㄹ12312323123213sdfdsfdsf","userId":"roy","timestamp":1631702791124},{"id":"d84df6fc-2d70-42fc-b7b9-a9a03cae80fa","text":"1231214ㅠㄹ율ㅇㅎ수정해봅시다.","userId":"roy","timestamp":1631700782333},{"id":"e2e03d12-2316-4697-b6ed-d7c230b1ab13","text":"sdfdsff123123131sdfsfsdfsdfcxv123123123","userId":"roy","timestamp":1631691838370},{"id":"b7d96402-9648-417c-b14a-4a0e8ce34748","text":"graphql playground에서 작성sdfewfew","userId":"roy","timestamp":1631566300856},{"id":"2e0c7e5e-7745-4b89-ae66-75ac67ec7839","text":"로이라는 닉네임으로 새글이 등록됩니다.","userId":"roy","timestamp":1631433146774},{"id":"398a4852-0d87-43e0-ae9d-d681d1dc2297","text":"제이라는 닉네임으로 새글이 등록됩니다.","userId":"jay","timestamp":1631433059221},{"id":"ae7ab8a5-8865-4a9f-9ab8-716eef2e9ec1","text":"새글 작성 id는 어떻게 요청가는지 봅시다.","userId":"jay","timestamp":1631288042010},{"id":"7e0ae0c5-048c-4902-af6b-ad324934c5ca","text":"새글 roy입니다.","userId":"roy","timestamp":1631261337522},{"id":"861d5b1e-596e-4596-8fd6-e5b4fadb07d1","text":"새글 jay입니다. 수정했습니다. 또 수정했습니다.","userId":"jay","timestamp":1631261326171},{"id":"9fe95b38-9f9b-4cb5-8336-941107b38a01","text":"새글입니다","userId":"roy","timestamp":1631261286845},{"id":"033c3344-e03f-40fa-8038-66fdec7d9bb5","text":"새글입니다","userId":"roy","timestamp":1631261275381},{"id":"a4653af2-808e-4751-8777-940c8f70fc46","text":"새글입니다.","userId":"roy","timestamp":1631261265660},{"id":"50","userId":"jay","timestamp":1234570890123,"text":"50 mock text"},{"id":"49","userId":"jay","timestamp":1234570830123,"text":"49 mock text"},{"id":"48","userId":"jay","timestamp":1234570770123,"text":"48 mock text id는 왜 이상하게 바뀔까"},{"id":"47","userId":"jay","timestamp":1234570710123,"text":"47 mock text"},{"id":"46","userId":"jay","timestamp":1234570650123,"text":"46 mock text"},{"id":"45","userId":"roy","timestamp":1234570590123,"text":"45 mock text"},{"id":"44","userId":"roy","timestamp":1234570530123,"text":"44 mock text"},{"id":"43","userId":"jay","timestamp":1234570470123,"text":"43 mock text"},{"id":"42","userId":"jay","timestamp":1234570410123,"text":"42 mock text"},{"id":"41","userId":"jay","timestamp":1234570350123,"text":"41 mock text"},{"id":"40","userId":"roy","timestamp":1234570290123,"text":"40 mock text"},{"id":"39","userId":"jay","timestamp":1234570230123,"text":"39 mock text"},{"id":"38","userId":"roy","timestamp":1234570170123,"text":"38 mock text"},{"id":"37","userId":"roy","timestamp":1234570110123,"text":"37 mock text"},{"id":"36","userId":"jay","timestamp":1234570050123,"text":"36 mock text"},{"id":"35","userId":"roy","timestamp":1234569990123,"text":"35 mock text"},{"id":"34","userId":"roy","timestamp":1234569930123,"text":"34 mock text"},{"id":"33","userId":"roy","timestamp":1234569870123,"text":"33 mock text"},{"id":"32","userId":"jay","timestamp":1234569810123,"text":"32 mock text"},{"id":"31","userId":"jay","timestamp":1234569750123,"text":"31 mock text"},{"id":"30","userId":"roy","timestamp":1234569690123,"text":"30 mock text"},{"id":"29","userId":"roy","timestamp":1234569630123,"text":"29 mock text"},{"id":"28","userId":"roy","timestamp":1234569570123,"text":"28 mock text"},{"id":"27","userId":"jay","timestamp":1234569510123,"text":"27 mock text"},{"id":"26","userId":"roy","timestamp":1234569450123,"text":"26 mock text"},{"id":"25","userId":"jay","timestamp":1234569390123,"text":"25 mock text"},{"id":"24","userId":"roy","timestamp":1234569330123,"text":"24 mock text"},{"id":"23","userId":"jay","timestamp":1234569270123,"text":"23 mock text"},{"id":"22","userId":"roy","timestamp":1234569210123,"text":"22 mock text"},{"id":"21","userId":"roy","timestamp":1234569150123,"text":"21 mock text"},{"id":"20","userId":"roy","timestamp":1234569090123,"text":"20 mock text"},{"id":"19","userId":"roy","timestamp":1234569030123,"text":"19 mock text"},{"id":"18","userId":"roy","timestamp":1234568970123,"text":"18 mock text"},{"id":"17","userId":"roy","timestamp":1234568910123,"text":"17 mock text"},{"id":"16","userId":"roy","timestamp":1234568850123,"text":"16 mock text"},{"id":"15","userId":"jay","timestamp":1234568790123,"text":"15 mock text"},{"id":"14","userId":"roy","timestamp":1234568730123,"text":"14 mock text"},{"id":"13","userId":"jay","timestamp":1234568670123,"text":"13 mock text"},{"id":"12","userId":"roy","timestamp":1234568610123,"text":"12 mock text"},{"id":"11","userId":"jay","timestamp":1234568550123,"text":"11 mock text"},{"id":"10","userId":"jay","timestamp":1234568490123,"text":"10 mock text"},{"id":"9","userId":"roy","timestamp":1234568430123,"text":"9 mock text"},{"id":"8","userId":"roy","timestamp":1234568370123,"text":"8 mock text"},{"id":"7","userId":"roy","timestamp":1234568310123,"text":"7 mock text"},{"id":"6","userId":"jay","timestamp":1234568250123,"text":"6 mock text"},{"id":"5","userId":"roy","timestamp":1234568190123,"text":"5 mock text"},{"id":"4","userId":"jay","timestamp":1234568130123,"text":"4 mock text"},{"id":"3","userId":"roy","timestamp":1234568070123,"text":"3 mock text"},{"id":"2","userId":"jay","timestamp":1234568010123,"text":"2 mock text"},{"id":"1","userId":"roy","timestamp":1234567950123,"text":"1 mock text"}],
"users": {
"roy": {"id": "roy", "nickname": "로이"},
"jay": {"id": "jay", "nickname": "제이"}
}
}
server/src/resolvers/users.js
이쪽은 dbController.js
를 불러와서 뭘 한게 없으므로 아래처럼 db
를 models
로만 수정하고 이대로 두면될거같습니다.
// server/src/resolvers/users.js
const userResolver = {
Query: {
- users: (parent, args, {db}) => Object.values(db.users),
+ users: (parent, args, {models}) => Object.values(models.users),
- user: (parent, {id}, {db}) => db.users[id],
+ user: (parent, {id}, {models}) => models.users[id],
}
}
export default userResolver
cd ../
yarn run server
yarn run client
새글을쓰고
새글이 db.json
에 기록되고
수정하면
수정이 되어있고 삭제하면 삭제한 것도 그즉시 db.json
에 반영이됩니다.
7.2 json-server
이번에는 json-server
라는 것을 이용해서 기존 express
로 만들어놓은 서버쪽을 변경을 해보도록 하겠습니다.
json-server
라고하는 npm
라이브러리는 zero configuration
을 표방하고있는 굉장히 편리한 라이브러리입니다.
그럼에도 불구하고 처음에 소개해드리지않고 맨 마지막으로 미룬 이유는 이번 강의를 보시면서 이해하실 수 있으리라 생각합니다.
cd server/
yarn add json-server
// server/src/index.js
- import express from 'express';
+ import jsonServer from 'json-server';
- import cors from 'cors'
import messagesRoute from "./routes/messages.js";
import usersRoute from "./routes/users.js";
- const app = express();
- app.use(express.urlencoded({extended: true}))
- app.use(express.json())
- app.use(cors({
- origin: 'http://localhost:3000',
- credentials: true,
- }))
// express 서버 대신 jsonServer를 create 합니다.
+ const app = jsonServer.create();
// 그리고 router라는게 있는데 이 router는 우리가 만들어놓은 아래 routes랑 별개로 jsonServer가 자동으로 db에있는 json 구조를 바탕으로 알아서 라우트를 만들어줍니다.
// 이것이 jsonServer가 제공하는 기능입니다.
+ const router = jsonServer.router('./src/db.json');
// 여기서 app.use로 jsonServer에 있는 bodyParser를 사용하겠다 라고 해줍니다.
// 이 부분은 request에 body가 내려오는데 이 body 부분에 접근하게끔 해주는 녀석입니다.
// 이 부분은 현재로썬 필요는 없는데 일단 주석처리해놓고 이따 필요할 때 열어놓고 보도록 하겠습니다.
// app.use(jsonServer.bodyParser);
// 아래 코드들도 일단 주석처리해놓고
// const routes = [...messagesRoute, ...usersRoute]
// routes.forEach(({method, route, handler}) => {
// app[method](route, handler)
// })
// 일단 이것만 넣도록 하겠습니다.
+ app.use(router);
app.listen(8000, () => {
console.log('server listening on 8000...')
})
여기까지 작성하고 보시면 현재로써 보시면 jsonServer
가 .router()
메소드를 통해 직접 db
(./src/db.json
)에 접근을했고,
이 db
에 있는 정보를 컨트롤하는 routes
에 대해서는 전혀 불러온 것이 없습니다.
그냥 db
에 접근을 했고 접근한걸 router
라는 변수에 담고
이 router
를 app.use(router)
사용하겠다 라고만했는데,
이것만으로 클라이언트와 서버를 실행시켜보도록 하겠습니다.
cd ../
yarn run server
localhost:8000/messages
로 들어가보겠습니다.
db
에 있는 데이터가 전부 불러와진 것을 확인하실 수 있습니다.
users
로도 접속해보시면 우리가 만들어놓은 db
정보가 잘 불러와지는 것을 볼 수 있습니다.
또 messages
로 가서 아무 id
나 하나 접근을 해볼게요.
위와 같이 바로 불러와지는걸 확인할 수 있습니다.
GET
관련해선 이렇게 접근하는 것이 가능하고, POST
, PUT
, DELETE
도 마찬가지로 jsonServer
가 제공하는 기능만으로 해결이 가능합니다.
가능은하지만, 클라이언트랑 같이 연동을 하려고보면 문제가있습니다.
yarn run client
localhost:3000/?userId=roy
에 접속해보겠습니다.
잘 불러와졌습니다.
json-server
를 실행한 것 만으로 routes
라는 거 없이도(messagesRoute
, usersRoute
) 잘 불러와졌는데,
문제는 cursor
지정 없이 최초 불러오는 메시지 리스트 갯수가
너무 많습니다.
마지막 데이터부터 맨 처음 데이터까지 전부를 불러온겁니다.
저희가 이미 무한스크롤링을 구현한 상태에서 15개씩 불러오는 동작이 제대로 작동하고있지 않은것이죠.
몇개씩 끊어서 불러오는 기능을 제공은 하고있습니다.
slice
도 할 수도있고, ~보다 큰값, ~보다 작은값 이렇게도 할 수 있는데,
이런 것들은 구현하려면 이 부분은 클라이언트에서 해줘야됩니다.
즉, cursor
값 대신에 _start
값 인덱스를 찾아내고..
그런 요청 자체를 클라이언트에서 요청을 이렇게 바꿔서 보내야하는겁니다.
이런걸 할 수 있다는겁니다.
40부터 50까지 메시지 요청.
즉, 0~15, 15~30 이렇게 요청을 보낼 수 있습니다.
이런식의 요청을.. 저희가 클라이언트쪽을 수정을하거나 서버쪽을 변경을 하거나 하는식의 작업이 필요합니다.
원래 json-server
가 처음에 말씀드린 것처럼 zero coding
, 30초만에 가능하다
라고하긴 했지만,
이거는 정말 얘네가 제공하고있는 기능만 쓰고자할 때 그런거고
그렇지않은 추가적인 기능들을 하려고하면
클라이언트에서 수정을하든 아니면 서버에서 내용을 추가로하든 결과적으로 express
와 큰 차이가 없게됩니다.
client/components/MsgList.js
// client/components/MsgList.js
import MsgItem from "./MsgItem";
import MsgInput from "./MsgInput";
import {useEffect, useRef, useState} from "react";
import {useRouter} from "next/router";
import fetcher from "../fetcher.js";
import useInfiniteScroll from "../hooks/useInfiniteScroll";
const MsgList = ({smsgs, users}) => {
const {query} = useRouter();
const userId = query.userId || query.userid || '';
const [msgs, setMsgs] = useState(smsgs);
const [editingId, setEditingId] = useState(null);
const [hasNext, setHasNext] = useState(true);
const fetchMoreEl = useRef(null);
const intersecting = useInfiniteScroll(fetchMoreEl)
const onCreate = async text => {
const newMsg = await fetcher('post', '/messages', {text, userId})
if (!newMsg) throw Error('something wrong')
setMsgs(msgs => ([newMsg, ...msgs]))
}
const onUpdate = async (text, id) => {
const newMsg = await fetcher('put', `/messages/${id}`, { text, userId })
if (!newMsg) throw Error('something wrong')
setMsgs(msgs => {
const targetIndex = msgs.findIndex(msg => msg.id === id);
if (targetIndex < 0) return msgs;
const newMsgs = [...msgs]
newMsgs.splice(targetIndex, 1, newMsg);
return newMsgs;
})
doneEdit();
}
const onDelete = async (id) => {
const receivedId = await fetcher('delete', `/messages/${id}`, { params: { userId }})
setMsgs(msgs => {
const targetIndex = msgs.findIndex(msg => msg.id === receivedId + '');
if (targetIndex < 0) return msgs;
const newMsgs = [...msgs]
newMsgs.splice(targetIndex, 1)
return newMsgs;
})
}
const doneEdit = () => setEditingId(null)
const getMessages = async () => {
// _start 값을 계산하는 것이 낫겠습니다. msgs 갯수가 15개면 15부터 불러오면 되는거니깐.
+ const _start = msgs.length;
// 그럼 _end는 _start에 15개를 더한값이 되면되겠죠?
+ const _end = _start + 15;
// getMessages를 요청할 때 params에 cursor 대신에 _start가 되면 되는겁니다.
- const newMsgs = await fetcher('get', '/messages', {params: {cursor: msgs[msgs.length - 1]?.id || ''}})
+ const newMsgs = await fetcher('get', '/messages', {params: {_start, _end}})
if (newMsgs.length === 0) {
setHasNext(false);
return
}
setMsgs([...msgs, ...newMsgs])
}
useEffect(() => {
if (intersecting && hasNext) getMessages()
}, [intersecting])
console.log('render')
return (
<>
{userId && <MsgInput mutate={onCreate}/>}
<ul className='messages'>
{
msgs.map(x => <MsgItem key={x.id}
{...x}
onUpdate={onUpdate}
onDelete={() => onDelete(x.id)}
startEdit={() => setEditingId(x.id)}
isEditing={editingId === x.id}
myId={userId}
user={users[x.userId]}
/>)
}
</ul>
<div ref={fetchMoreEl} />
</>
)
}
export default MsgList;
위 수정사항은 무한스크롤을 할 때의 문제고, 최초 접속시를 고려해서
client/pages/index.js
// client/pages/index.js
import MsgList from "../components/MsgList";
import fetcher from "../fetcher";
const Home = ({smsgs, users}) => (
<>
<h1>SIMPLE SNS</h1>
<MsgList smsgs={smsgs} users={users} />
</>
)
export const getServerSideProps = async () => {
- const smsgs = await fetcher('get', '/messages')
+ const smsgs = await fetcher('get', '/messages?_start=0&_end=15')
const users = await fetcher('get', '/users')
return {
props: {smsgs, users}
}
}
export default Home;
위와 같이 수정해놓고 확인해보겠습니다.
처음에 15개가 불러와지고
스크롤을 맨 아래로 내리니깐 CORS
에러가 납니다.CORS
를 넣어줘야하나봅니다.
// server/src/index.js
import jsonServer from 'json-server';
+ import cors from 'cors';
import messagesRoute from "./routes/messages.js";
import usersRoute from "./routes/users.js";
const app = jsonServer.create();
const router = jsonServer.router('./src/db.json');
+ app.use(
+ cors({
+ origin: 'http://localhost:3000',
+ credentials: true,
+ })
+ )
app.use(router);
app.listen(8000, () => {
console.log('server listening on 8000...')
})
위 상태로 다시 확인해보겠습니다.
이제는 문제없이 무한스크롤 기능이 작동합니다.
POST 확인
POST를 확인해보겠습니다.
넘어온 요청에 timestamp
요청이 없습니다.
그래서 화면에 invalid Data
라고 뜨고있습니다.
방금 위 상황에서 새로고침을 했더니 방금 썼던 글이 없어졌죠?
그런데 이게 없어진게아니고,
스크롤을 맨 아래로 내리면, 맨 아래에 방금 썼던 글이 있습니다.
맨 밑으로간 이유는 json-server
에서 제공하는 방식이 무조건 선형적으로 맨밑에 내용이 추가되게끔 짜여진 형태이기 때문입니다.
그럼 저희가 만들어놓은 방식이 안되는거죠.
즉, DB 구조를 바꿔야되는 상황이 온겁니다.
읽어올 때 order
를 지정해야되는거죠.
order
는 timestamp
에 따라 지정이되게끔 order
를 해보도록 하겠습니다.
위와 같이 _sort
기준과 _order
을 어떻게할건지 정해주면된다.
// client/components/MsgList.js
import MsgItem from "./MsgItem";
import MsgInput from "./MsgInput";
import {useEffect, useRef, useState} from "react";
import {useRouter} from "next/router";
import fetcher from "../fetcher.js";
import useInfiniteScroll from "../hooks/useInfiniteScroll";
const MsgList = ({smsgs, users}) => {
const {query} = useRouter();
const userId = query.userId || query.userid || '';
const [msgs, setMsgs] = useState(smsgs);
const [editingId, setEditingId] = useState(null);
const [hasNext, setHasNext] = useState(true);
const fetchMoreEl = useRef(null);
const intersecting = useInfiniteScroll(fetchMoreEl)
const onCreate = async text => {
const newMsg = await fetcher('post', '/messages', {text, userId})
if (!newMsg) throw Error('something wrong')
setMsgs(msgs => ([newMsg, ...msgs]))
}
const onUpdate = async (text, id) => {
const newMsg = await fetcher('put', `/messages/${id}`, { text, userId })
if (!newMsg) throw Error('something wrong')
setMsgs(msgs => {
const targetIndex = msgs.findIndex(msg => msg.id === id);
if (targetIndex < 0) return msgs;
const newMsgs = [...msgs]
newMsgs.splice(targetIndex, 1, newMsg);
return newMsgs;
})
doneEdit();
}
const onDelete = async (id) => {
const receivedId = await fetcher('delete', `/messages/${id}`, { params: { userId }})
setMsgs(msgs => {
const targetIndex = msgs.findIndex(msg => msg.id === receivedId + '');
if (targetIndex < 0) return msgs;
const newMsgs = [...msgs]
newMsgs.splice(targetIndex, 1)
return newMsgs;
})
}
const doneEdit = () => setEditingId(null)
const getMessages = async () => {
const _start = msgs.length;
const _end = _start + 15;
- const newMsgs = await fetcher('get', '/messages', {params: {_start, _end}})
+ const newMsgs = await fetcher('get', '/messages', {params: {_start, _end, _sort: 'timestamp', _order: 'desc'}})
if (newMsgs.length === 0) {
setHasNext(false);
return
}
setMsgs([...msgs, ...newMsgs])
}
useEffect(() => {
if (intersecting && hasNext) getMessages()
}, [intersecting])
console.log('render')
return (
<>
{userId && <MsgInput mutate={onCreate}/>}
<ul className='messages'>
{
msgs.map(x => <MsgItem key={x.id}
{...x}
onUpdate={onUpdate}
onDelete={() => onDelete(x.id)}
startEdit={() => setEditingId(x.id)}
isEditing={editingId === x.id}
myId={userId}
user={users[x.userId]}
/>)
}
</ul>
<div ref={fetchMoreEl} />
</>
)
}
export default MsgList;
// client/pages/index.js
import MsgList from "../components/MsgList";
import fetcher from "../fetcher";
const Home = ({smsgs, users}) => (
<>
<h1>SIMPLE SNS</h1>
<MsgList smsgs={smsgs} users={users} />
</>
)
export const getServerSideProps = async () => {
- const smsgs = await fetcher('get', '/messages?_start=0&_end=15')
+ const smsgs = await fetcher('get', '/messages?_start=0&_end=15&_sort=timestamp&_order=desc')
const users = await fetcher('get', '/users')
return {
props: {smsgs, users}
}
}
export default Home;
우선 새글을 추가할 때 그 새글에 timestamp
가 들어가도록하는 것이 관건인거 같습니다.
이젠 새글 등록하고 새로고침해도 위로 오지만invalid data
라 invalid data
로 등록된 새글끼리는 순서가 엉킨다.invalid data
가 안들어가고 timestamp
가 들어가도록 수정해보도록 하겠다.
지금 삭제 버튼을 눌렀는데도 별도의 data
가 응답되지 않고 있습니다.
그래서 원래 id
기반으로 삭제하던 방식을 못하고 다른 방식으로 삭제를 구현해야됩니다.
이런식으로 기존 client
에서 작업했던 것과는 다른 내용이 너무 많아서 이것들을 하나하나 커스터마이징 하다보면 많이 스트레스를 받을 상황인거 같습니다.
그래도 일단 POST
에 대해서만 보여드리고 넘어가도록 하겠습니다.
POSt
에 timestamp
를 넣으려면 2가지 방법이 있습니다.
timestamp 넣는 첫번째 방법
첫번째로 app.use()
로 json-server API
에서 얘기해주고있는 Custom routes example
부분의 위 드래그한 부분이랑 똑같이하면됩니다.
// server/src/index.js
import jsonServer from 'json-server';
import cors from 'cors';
import messagesRoute from "./routes/messages.js";
import usersRoute from "./routes/users.js";
const app = jsonServer.create();
const router = jsonServer.router('./src/db.json');
app.use(
cors({
origin: 'http://localhost:3000',
credentials: true,
})
)
// 그럴려면 아까 주석처리해놓은 아래 부분을 다시 살려놔야됩니다.
+ app.use(jsonServer.bodyParser);
// 이렇게 if, else에 의해 처리할 수 있습니다.
// req.body.timestamp로 Date.now() 값을 넘기면됩니다.
+ app.use((req, res, next) => {
+ if (req.method === 'POST') {
+ req.body.timestamp = Date.now()
+ }
+ // Continue to JSON Server router
+ next()
+ })
app.use(router);
app.listen(8000, () => {
console.log('server listening on 8000...')
})
이제 위와 같이 새글을 쓰면 시간이 지정이됩니다.
새로고침해도 순서 또한 잘 나옵니다.db
상에선 최신 글이 맨 밑에 있겠지만, 화면에뿌릴땐 timestamp
기준으로 desc
로 order
를 했기 때문에 최신글이 맨 위로 오게됩니다.
timestamp 2번째 방법 - 기존방법 그대로 살리는 방법
server/src/index.js
기존 상태인 아래 상태에서
// server/src/index.js
import jsonServer from 'json-server';
import cors from 'cors';
import messagesRoute from "./routes/messages.js";
import usersRoute from "./routes/users.js";
const app = jsonServer.create();
const router = jsonServer.router('./src/db.json');
app.use(
cors({
origin: 'http://localhost:3000',
credentials: true,
})
)
// 기존 상태에서 아래 부분을 살려보겠습니다.
// 아래 ...usersRoute는 일단 지우고..
+ const routes = messagesRoute;
+ routes.forEach(({method, route, handler}) => {
+ app[method](route, handler)
+ })
app.use(router);
app.listen(8000, () => {
console.log('server listening on 8000...')
})
server/src/routes
부분을 살리면됩니다.
GET
, PUT
, DELETE
모두 주석처리하고 POST
만 살려둡니다.
// server/src/routes/messages.js
import {v4} from "uuid";
import db from "../dbController.js";
const getMsgs = () => {
db.read();
db.data = db.data || {messages: []};
return db.data.messages;
}
const messagesRoute = [
// ...
{ // CREATE MESSAGE
method: 'post',
route: '/messages',
handler: ({body}, res) => {
try {
if (!body.userId) throw Error('no userId');
const msgs = getMsgs();
const newMsg = {
id: v4(),
text: body.text,
userId: body.userId,
timestamp: Date.now(),
}
db.data.messages.unshift(newMsg);
db.write();
res.send(newMsg)
} catch (err) {
res.status(500).send({error: err})
}
}
},
// ...
]
export default messagesRoute
그럼 POST
관련 요청에선 작동을 할겁니다.
위와 같이 수정한 상태에서 새글을 POST
했더니 위와 같은 에러가 떴습니다.
// server/src/index.js
import jsonServer from 'json-server';
import cors from 'cors';
import messagesRoute from "./routes/messages.js";
import usersRoute from "./routes/users.js";
const app = jsonServer.create();
const router = jsonServer.router('./src/db.json');
app.use(
cors({
origin: 'http://localhost:3000',
credentials: true,
})
)
// routes를 살리기위해선 아래 코드를 살려야합니다.
+ app.use(jsonServer.bodyParser);
const routes = messagesRoute;
routes.forEach(({method, route, handler}) => {
app[method](route, handler)
})
app.use(router);
app.listen(8000, () => {
console.log('server listening on 8000...')
})
이제 POST
가 잘 동작합니다.
이때는 lowdb
에 의해서 db.json
내에 내용이 써진거고,POST
도 저희가만든 routes
에 의해서 동작을 하게됩니다.
json-server
를 이용한다고하긴 했지만, 사실상 이 POST
는 json-server
에서 제공한 기능을 쓴 것이 아니고,
커스터마이징, 별도의 기능을 쓴 것입니다.POST
기능은 기존 express
를 썼을 때와 완전히 동일한 기능인거죠.
PUT
아까 주석처리한 put
을 살립니다.
// server/src/routes/messages.js
import {v4} from "uuid";
import db from "../dbController.js";
const getMsgs = () => {
db.read();
db.data = db.data || {messages: []};
return db.data.messages;
}
const messagesRoute = [
// ...
{ // CREATE MESSAGE
method: 'post',
route: '/messages',
handler: ({body}, res) => {
try {
if (!body.userId) throw Error('no userId');
const msgs = getMsgs();
const newMsg = {
id: v4(),
text: body.text,
userId: body.userId,
timestamp: Date.now(),
}
db.data.messages.unshift(newMsg);
db.write();
res.send(newMsg)
} catch (err) {
res.status(500).send({error: err})
}
}
},
{ // UPDATE MESSAGE
method: 'put',
route: '/messages/:id',
handler: ({body, params: {id}}, res) => {
try {
const msgs = getMsgs();
const targetIndex = msgs.findIndex(msg => msg.id === id)
if (targetIndex < 0) throw '메시지가 없습니다.'
if (msgs[targetIndex].userId !== body.userId) throw '사용자가 다릅니다.'
const newMsg = {
...msgs[targetIndex],
text: body.text,
}
db.data.messages.splice(targetIndex, 1, newMsg);
db.write();
res.send(newMsg)
} catch (err) {
res.status(500).send({error: err})
}
}
},
// ...
]
export default messagesRoute
put
동작이 아주 잘됩니다.
DELETE
아직 routes
에 delete
내용 안 살렸습니다.
안 살린 상태로 delete
는 삭제버튼 누르면 화면에 반영은 안되지만 삭제가 되긴 됩니다.
새로고침하면 반영이 되어있습니다.
화면에 반영이 되게하려면 다음과 같이 하면 됩니다.
// client/components/MsgList.js
import MsgItem from "./MsgItem";
import MsgInput from "./MsgInput";
import {useEffect, useRef, useState} from "react";
import {useRouter} from "next/router";
import fetcher from "../fetcher.js";
import useInfiniteScroll from "../hooks/useInfiniteScroll";
const MsgList = ({smsgs, users}) => {
const {query} = useRouter();
const userId = query.userId || query.userid || '';
const [msgs, setMsgs] = useState(smsgs);
const [editingId, setEditingId] = useState(null);
const [hasNext, setHasNext] = useState(true);
const fetchMoreEl = useRef(null);
const intersecting = useInfiniteScroll(fetchMoreEl)
const onCreate = async text => {
const newMsg = await fetcher('post', '/messages', {text, userId})
if (!newMsg) throw Error('something wrong')
setMsgs(msgs => ([newMsg, ...msgs]))
}
const onUpdate = async (text, id) => {
const newMsg = await fetcher('put', `/messages/${id}`, { text, userId })
if (!newMsg) throw Error('something wrong')
setMsgs(msgs => {
const targetIndex = msgs.findIndex(msg => msg.id === id);
if (targetIndex < 0) return msgs;
const newMsgs = [...msgs]
newMsgs.splice(targetIndex, 1, newMsg);
return newMsgs;
})
doneEdit();
}
const onDelete = async (id) => {
// receivedId가 없으므로 지우고
- const receivedId = await fetcher('delete', `/messages/${id}`, { params: { userId }});
+ await fetcher('delete', `/messages/${id}`, { params: { userId }});
setMsgs(msgs => {
- const targetIndex = msgs.findIndex(msg => msg.id === receivedId + '');
+ const targetIndex = msgs.findIndex(msg => msg.id === id);
if (targetIndex < 0) return msgs;
const newMsgs = [...msgs]
newMsgs.splice(targetIndex, 1)
return newMsgs;
})
}
const doneEdit = () => setEditingId(null)
const getMessages = async () => {
const _start = msgs.length;
const _end = _start + 15;
const newMsgs = await fetcher('get', '/messages', {params: {_start, _end, _sort: 'timestamp', _order: 'desc'}})
if (newMsgs.length === 0) {
setHasNext(false);
return
}
setMsgs([...msgs, ...newMsgs])
}
useEffect(() => {
if (intersecting && hasNext) getMessages()
}, [intersecting])
console.log('render')
return (
<>
{userId && <MsgInput mutate={onCreate}/>}
<ul className='messages'>
{
msgs.map(x => <MsgItem key={x.id}
{...x}
onUpdate={onUpdate}
onDelete={() => onDelete(x.id)}
startEdit={() => setEditingId(x.id)}
isEditing={editingId === x.id}
myId={userId}
user={users[x.userId]}
/>)
}
</ul>
<div ref={fetchMoreEl} />
</>
)
}
export default MsgList;
위와 같이 수정하고 삭제버튼을 누르면 삭제가 잘됩니다.
이렇게 json-server
에서 제공하는 기능으로 구현하니까 routes
부분에 delete
는 필요없게됐습니다.
GET
쪽은 클라이언트쪽의 내용을 수정함으로써 해결을 했고client/components/MsgList.js
,client/pages/index.js
POST
는 기존routes
를 살림으로써 해결을 했고PUT
도 기존routes
를 살림으로써 해결을 했고DELETE
는 클라이언트를 수정함으로써 해결을 했습니다.client/components/MsgList.js
server/src/routes/messages.js
라우트를 보면 많이 삭제를 하긴 했거든요?
이 파일의 코드량은 많이 줄긴했지만, 오히려 코드가 여러곳에 산재되어있어 복잡하게되어버리니까
저는 개인적으로 json-server
를 이용하는 것이 어떤 큰 매력이 있는건지 잘 모르겠습니다.
정말 간단하게 db
를 만들어놓고 그 db
에서 json-server
가 제공하는 기능만으로 모든 것이 해결 가능한, 정말 간단한 클라이언트를 만들 때는 json-server
가 굉장히 유용할 수 있는데,
그렇지않고 조금이라도 커스터마이징을 해야된다라고 하면 express
로 만드는 것이 훨씬 더 속편하지않을까 라고 생각을 합니다.
클라이언트도 수정하고 서버도 수정하느니 기존꺼 그대로 쓰는거랑 별반 다를게 없지않나싶은 의견입니다.
이거는 여러분들 각자의 상황에 맞게 json-server
를 쓰실지 express
를 쓰실지 판단하시면됩니다.
같은 맥락에서 json-graphql-server
라는 라이브러리도 있습니다.
얘도 마찬가지로 zero configuration
을 얘기하고 있는데, 이것도 결과적으로 하다보면 여러 손대야할 곳들이 많이 생기더라구요.
이거는 써보실 분들은 써보시고, 저라면 그냥 express
기반으로 하는 것을 추천드립니다.
그 이유에 대해서 다시 또 말씀드리자면, 실무에서는 사실상 json-server
를 아예 사용을 안할 것입니다.
왜냐면 json-server
라는 애는 자체적으로 목 데이터를 기반으로 빠르게 프론트엔드 개발자들이 연습하기위해 제공하는 그런 기능들만 할 수 있게끔 해주는 것이 전부이기 때문에
실무에선 애초부터 쓸 수가 없는거죠?
반대로 express
의 경우는 실무에서 쓰는 경우가 굉장히 많습니다.
프론트엔드 개발자는 express
라는 프레임워크를 실제로 다룰일이 많지 않을 수도 있지만,
서버개발자가 작업해놓은 내용들을 같이 살펴보면서 똑같이 이해할 수는 있는 것이죠.
그럴 때 이런 express
프레임워크를 실제로 써보신 경험이 있고없고는 굉장히 큰 차이가 있을 것입니다.
그런 맥락에서 express
쓰시는걸 권장드리고 싶습니다.