Mi Lugarcito
React & Mysql 트위터 만들기 - backend 편 본문
front : 3000/ back : 3065
npm init // package.json -> npm install or npm i -> download
npm i express // server
npm i sequelize sequelize-cli mysql2 // server
npx sequelize init //server
npx sequelize db:create // server
node app //server
npm i -D nodemon@2 //server
npm i bcrypt // password, server
npm i cors // server, middleware 설치하기
npm i passport passport-local //이메일, 아이디 로그인
npm i express-session // session 설치
npm i cookie-parser // cookie
npm i dotenv //server
// server, 요청과 응답을 기록하는 라이브러리, front->back 요청 보낼때 어떤 요청들을 보냈는지 콘솔창에 알려준다.
npm i morgan
npm i multer//이미지 때문
REST API
app.get //back으로 데이터 못넘김
app.post
app.put // 전체수정
app.delete// back으로 데이터 못넘김
app.patch //부분수정 (예 : 닉네임만 수정)
app.options // 찔러보기...
app.head //헤더 또는 바디만 가져오기
//주소 부분에서 동적으로 계속 바뀌는 부분 :postId ? 파라미터라고 부른다, 그리고 주소에 들어가는 모든것들은 숫자처럼 보여도 전부 문자열이다.
router.post('/:postId/commnet', async (req,res, next)=>{//POST /post/comment
//아래와 같이!!
PostId : parseInt(req.params.postId),
요청/응답 : 헤더(상태, 용량, 시간, 쿠키) 와 바디 (데이터)로 구성됨
200 : 성공
300 : 리다이렉트
400 : 클라이언트 에러
500 : 서버 에러, 예 : next(error)
config.json
{
"development": {
"username": "root",
"password": "1234",
"database": "react-nodebird",
"host": "127.0.0.1",
"dialect": "mysql"
},
"test": {
"username": "root",
"password": null,
"database": "react-nodebird",
"host": "127.0.0.1",
"dialect": "mysql"
},
"production": {
"username": "root",
"password": null,
"database": "react-nodebird",
"host": "127.0.0.1",
"dialect": "mysql"
}
}
index.js
const Sequelize = require('sequelize');
const env = process.env.NODE_ENV || 'development';
const config = require('../config/config')[env];//config.json
const db={};
//sequelize 가 node & mysql 두개를 연결시켜준다
const sequelize = new Sequelize(config.database, config.username, config.password, config);
db.User = require('./user')(sequelize, Sequelize);
db.Post = require('./post')(sequelize, Sequelize);
db.Comment = require('./comment')(sequelize, Sequelize);
db.Hashtag = require('./hashtag')(sequelize, Sequelize);
db.Image = require('./image')(sequelize, Sequelize);
Object.keys(db).forEach(modelName => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;
user
//middleware라서 위->아래 순차적으로 실행된다. 위치 조심!
const express =require('express');//import
const {User, Post} = require('../models');
const bycrypt = require('bcrypt');//비번 암호
const router = express.Router();
const passport = require('passport');
//login 전략
//return done(null, false, {reason : '존재하지 않음'})
//middleware 확장하는 방법
router.post('/login', (req,res,next)=> {
//2. saga에서 받은 데이터를 가지고 전략을 실행함
passport.authenticate('local', (err, user, info)=>{
//5. local 전략 완료 후 여기서 로긴 실행하기
if(err){
//서버에러
console.log(err);
return next(err);
}
if(info){
//client error
return res.status(403).send(info.reason);
}
//req.login 시 passport로 로그인 한다. -> 세션 로그인 저장해줌
//6. 여기서 passport login 한다 -> passport 실행은 index.js에서
return req.login(user, async (loginerr) => {
if(loginerr){
console.log(loginerr);
return next(loginErr);
}
const fullUserWithoutPassword = await User.findOne({
where : { id : user.id },
attributes : {
exclude : ['password']
},
include : [
{
model : Post, // hasMany라서 Post -> 복수형이 되어 Posts로 된다.
},
{
model : User,
as : 'Followings'
},
{
model : User,
as : 'Followers'
},
],
})
return res.status(200).json(fullUserWithoutPassword);
})
})(req,res,next);
});
router.post('/logout', (req,res)=>{
req.logout();
req.session.destroy();//session 에 저장된 쿠키, 아이디를 없애기
res.send('ok');//logout
})
router.post('/', async (req,res,next)=>{//POST : /user/
//비동기는 대부분 try-catch 로 감싸주기
//(비)동기 함수인지는 공식 문서 찾아봐야함
//front saga에서 data.email, data.nickname
try {
const exUser = await User.findOne({
//조건문 where
where:{
email : req.body.email,
}
});
//요청/응답 : 헤더(상태, 용량, 시간, 쿠키) 와 바디 (데이터)로 구성됨
if(exUser){
return res.status(403).send('이미 사용중')//return 중요!
}
const hashedPassword = await bycrypt.hash(req.body.password, 12);
await User.create({//실행 --1
email : req.body.email ,
nickname : req.body.nickname ,
password: hashedPassword,
})
//res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3060');//혹은 주소 대신'*'
res.status(201).send('ok');//실행 --2
} catch (error) {
console.log(error);
next(error);//error 한방에 처리해줌, 500번대 서버 에러
}
});
module.exports = router;
//로그인시 내부적으로 res.setHeader('Cookie', 'cxlhy')이런식으로 쿠키 서버로 보내주고 알아서 세션과 연결해준다.
//8. 쿠키, 유저 아이디 서버에서 들고있고 프론트에 보내준다
//user 사용자 정보를 프론트로 넘기기-> 프론트에서 action.data로 받고 -> reducer
//1. saga 에서 데이터 보내면 ->
//2. server routes user.js로 전송 son(user); 로 서버가 받음->
//3. saga에서 action.data로 받고 ->
//4. reducer 에서는 me로 받음
//fullUserWithoutPassword에는 내 게시글숫자, 팔로잉 정보들때문에 따로 더 추가함, 비번 전달 제외하고(attributes : exclude)!
post
module.exports = (sequelize, DataTypes)=>{
const Post = sequelize.define('Post', {
content:{
type: DataTypes.TEXT,
allowNull:false,
},
},
{
charset : 'utf8mb4',//이모지
collate : 'utf8mb4_general_ci'//한글저장
});
Post.associate = (db)=>{
db.Post.belongsTo(db.User);//속해있다
db.Post.belongsToMany(db.Hashtag, {through:'PostHashtag'});//다 : 다
db.Post.hasMany(db.Comment);
db.Post.hasMany(db.Image);
//나중에 as에 따라서 post.getLikers 처럼 게시글 좋아요 누른 사람을 가져오게 된다. 별칭같은것@@@
db.Post.belongsToMany(db.User, {through : 'Like' , as : 'Likers'});//다 : 다, 좋아요, through -> 중간테이블 이름 바꾸기
//게시글 간 끼리twitt(원본 : 복사글, 1:다)
db.Post.belongsTo(db.Post, {as : 'Retweet'}); // PostId -> RetweetId 로 바뀜
};
return Post;
}
comment
module.exports = (sequelize, DataTypes)=>{
const Comment = sequelize.define('Comment', {
content:{
type:DataTypes.TEXT,
allowNull:false,
},
},
//belongsTo 역할
//UserId:{}
//PostId:{}
{
charset : 'utf8mb4',//이모지
collate : 'utf8mb4_general_ci'//한글저장
});
Comment.associate = (db)=>{
db.Comment.belongsTo(db.User);//속해있다
db.Comment.belongsTo(db.Post);//속해있다
};
return Comment;
}
image
module.exports = (sequelize, DataTypes)=>{
const Image = sequelize.define('Image', {
src:{
type:DataTypes.STRING(200),
allowNull:false,
},
},
{
charset : 'utf8',//이모지
collate : 'utf8_general_ci'//한글저장
});
Image.associate = (db)=>{
db.Image.belongsTo(db.Post);
};
return Image;
}
hashtag
module.exports = (sequelize, DataTypes)=>{
const Hashtag = sequelize.define('Hashtag', {
name:{
type:DataTypes.STRING(20),
allowNull:false,
},
},
{
charset : 'utf8mb4',//이모지
collate : 'utf8mb4_general_ci'//한글저장
});
Hashtag.associate = (db)=>{// 다 : 다
db.Hashtag.belongsToMany(db.Post, {through:'PostHashtag'});//다 : 다
};
return Hashtag;
}
app.js
const express =require('express');
const postRouter = require('./routes/post');
const db = require('./models');
const app = express();
db.sequelize.sync()
.then(()=>{
console.log('db연결성공!!!')
})
.catch((error)=>
console.log(error));
app.use('/post',postRouter);
app.listen(3065, ()=>{
console.log('실행중....')
});
여기까지 하고 node.app 실행시키면
CORS 문제
: 브라우저 -> 백엔드서버, 다른 서버로 보낼때 생긴다. (브라우저가 차단해버림) 브라우저 -> 백엔드
: 서버->서버로 보낼땐 문제 없음, 브라우저 -> 프론트 ->백엔드 이렇게는 문제 없음 (proxy 방식)
: 차단은 브라우저가, 허용은 서버에서 허용해달라고 브라우저에게 요청하는 방법도 있음
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3060');//혹은 주소 대신'*'
cors middleware
app.use(cors({
origin : '*',//* 는 모두 허용, origin : true로 설정하면 * 대신 보낸 곳의 주소가 자동으로 들어가 편라함
credentials : false,//나중에 true 로 설정하기
}));//middleware
COOKIE & SESSION
백엔드 서버가 유저 정보를 브라우저한테 통째로 보내주는데 비밀번호 같은거는 랜덤한 문자 = 랜덤한 토큰으로 보내준다. (이게 바로 쿠키)
서버쪽은 이 쿠키랑 연결되어 있다.
이게 바로 브라우저 쿠키와 서버쪽 세션이다. (브라우저->서버로 쿠키도 같이 보내줄때 서버는 쿠키를 읽어서, 분해해서 어떤 사용자인지 알 수 있다.)
서버측에서는 메모리를 아끼기 위해서 쿠키랑 유저 아이디 정보만 서버에 저장, 나중에 디비에서 데이터 복구함
원리 : 브라우저와 백엔드 서버의 도메인이 다르면 cors 문제가 발생 & 쿠키도 전달이 안된다. -> 백엔드 서버는 어리둥절...
cors 이슈 해결방법 : proxy 설정 / header 로 cors 모듈 활용
app.use(cors({
origin : '*',//* 는 모두 허용, origin : true로 설정하면 * 대신 보낸 곳의 주소가 자동으로 들어가 편라함
credentials : false,//나중에 true 로 설정하기
}));//middleware
문제 : 쿠키가 전달이 안되면 백엔드 서버가 요청 보낸사람이 누군지 알 수 있는데, 로그인을 했다고 하더라도 그다음 요청이 백엔드 서버는 누가 보냈는지 몰라!
쿠키를 다른 도메인간 전달 해결 방법 :
---- server
app.use(cors({
origin : '*',//* 는 모두 허용, origin : true로 설정하면 * 대신 보낸 곳의 주소가 자동으로 들어가 편라함
credentials : true,//나중에 true 로 설정하기 (false 하면 서버로 쿠키가 전달 안됨)
}));//middleware
---- front
//공통적용
axios.defaults.baseURL = 'http://localhost:3065';
axios.defaults.withCredentials=true;//백 & 서버 쿠키 공유 해결방법
여기까지 설정하고 로그인 시도했는데 로그인이 에러가 뜬다.
여기에서의 * 는 브라우저에 아무나 접근이 가능하도록 하겠다는 의미인데, 이제는 무조건 주소를 적어라는 이야기.
즉, 프론트-백엔드에서 쿠키 = 민감한 정보를 오고가게 하는걸 좀 더 엄격하게 관리하겠다는 이야기
origin 에서 프론트 서버 포트 적어주기
app.use(cors({
//front server!!
origin : 'http://localhost:3000',//* 는 모두 허용, origin : true로 설정하면 * 대신 보낸 곳의 주소가 자동으로 들어가 편라함
credentials : true,//나중에 true 로 설정하기 (false 하면 서버로 쿠키가 전달 안됨)
}));//middleware
ps) SEQUALIZE 부터 받는 DATA는 자바스크립트 객체로 받는다. -> 이걸 toJson을 사용해서 바꿔줘야 한다.
user router
//middleware라서 위->아래 순차적으로 실행된다. 위치 조심!
const express =require('express');//import
const {User, Post} = require('../models');
const bycrypt = require('bcrypt');//비번 암호
const router = express.Router();
const passport = require('passport');
//login 전략
//return done(null, false, {reason : '존재하지 않음'})
//middleware 확장하는 방법
router.post('/login', (req,res,next)=> {
//2. saga에서 받은 데이터를 가지고 전략을 실행함
passport.authenticate('local', (err, user, info)=>{
//5. local 전략 완료 후 여기서 로긴 실행하기
if(err){
//서버에러
console.log(err);
return next(err);
}
if(info){
//client error
return res.status(403).send(info.reason);
}
//req.login 시 passport로 로그인 한다. -> 세션 로그인 저장해줌
//6. 여기서 passport login 한다 -> passport 실행은 index.js에서
return req.login(user, async (loginerr) => {
if(loginerr){
console.log(loginerr);
return next(loginErr);
}
const fullUserWithoutPassword = await User.findOne({
where : { id : user.id },
attributes : {
exclude : ['password']
},
include : [
{
model : Post, // hasMany라서 Post -> 복수형이 되어 Posts로 된다.
},
{
model : User,
as : 'Followings'
},
{
model : User,
as : 'Followers'
},
],
})
return res.status(200).json(fullUserWithoutPassword);
})
})(req,res,next);
});
router.post('/user/logout', (req,res)=>{
req.logout();
req.session.destroy();//session 에 저장된 쿠키, 아이디를 없애기
res.send('ok');//logout
})
router.post('/', async (req,res,next)=>{//POST : /user/
//비동기는 대부분 try-catch 로 감싸주기
//(비)동기 함수인지는 공식 문서 찾아봐야함
//front saga에서 data.email, data.nickname
try {
const exUser = await User.findOne({
//조건문 where
where:{
email : req.body.email,
}
});
//요청/응답 : 헤더(상태, 용량, 시간, 쿠키) 와 바디 (데이터)로 구성됨
if(exUser){
return res.status(403).send('이미 사용중')//return 중요!
}
const hashedPassword = await bycrypt.hash(req.body.password, 12);
await User.create({//실행 --1
email : req.body.email ,
nickname : req.body.nickname ,
password: hashedPassword,
})
//res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3060');//혹은 주소 대신'*'
res.status(201).send('ok');//실행 --2
} catch (error) {
console.log(error);
next(error);//error 한방에 처리해줌, 500번대 서버 에러
}
});
module.exports = router;
//로그인시 내부적으로 res.setHeader('Cookie', 'cxlhy')이런식으로 쿠키 서버로 보내주고 알아서 세션과 연결해준다.
//8. 쿠키, 유저 아이디 서버에서 들고있고 프론트에 보내준다
//user 사용자 정보를 프론트로 넘기기-> 프론트에서 action.data로 받고 -> reducer
//1. saga 에서 데이터 보내면 ->
//2. server routes user.js로 전송 son(user); 로 서버가 받음->
//3. saga에서 action.data로 받고 ->
//4. reducer 에서는 me로 받음
//fullUserWithoutPassword에는 내 게시글숫자, 팔로잉 정보들때문에 따로 더 추가함, 비번 전달 제외하고(attributes : exclude)!
local.js
//local login 전략
const passport = require('passport');
const {Strategy : LocalStrategy } =require('passport-local');
const {User} = require('../models');
const bcrypt =require('bcrypt');
//index.js 에서 local()에서 실행됨
module.exports =()=>{
passport.use(new LocalStrategy({
//프론트.data -> 서버.req.body 에 대한 설정
//3. 전략 세우기
usernameField: 'email',
passwordField: 'password',
}, async (email, password, done)=>{
//비동기 요청시 항상 감싸주기
try {
//함수 전략
const user = await User.findOne({
where:{email}
});
if(!user){
//결과 판단 : done (서버에러, 성공, 클라이언트 에러)
return done(null, false, {reason : '존재하지 않음'})
}
const result = await bcrypt.compare(password, user.password);
if(result){
return done(null, user);//4. done 은 callback 함수 -> routes.user.js에서 로그인으로 넘겨주기
}
return done(null, false, {reason : '비밀번호가 틀렸다.'})
} catch (error) {
console.log(error);
return done(error);
}
}));
}
index.js
const passport = require('passport');
const local=require('./local');
const {User} = require('../models');
//app.js 에서 실행된다.
//passport login 실행공간
module.exports=()=>{
//passport login 실행공간
//7. 쿠키랑 유저 아이디 정보만 서버에 저장
passport.serializeUser((user, done)=>{
done(null, user.id)
});
//9. 로그인 성공후 아이디로부터 사용자 정보를 디비를 통해서 복구한다.
//로그인 한 이후부터 router 실행되기 전에 매번 실행된다.
passport.deserializeUser(async (id, done)=>{
try {
const user = await User.findOne({where : {id}});
done(null, user) // 이 정보를 req.user 안에 내 정보 넣어준다.
} catch (error) {
console.error(error);
done(error);
}
});
local();
}
app.js
const express = require('express');
const cors = require('cors');
const session = require('express-session');
const cookieParser = require('cookie-parser');
const passport = require('passport');
const dotenv = require('dotenv');
const postRouter = require('./routes/post');
const userRouter = require('./routes/user');
const db = require('./models');
const passportConfig = require('./passport');
dotenv.config();
const app = express();
db.sequelize.sync()
.then(() => {
console.log('db 연결 성공');
})
.catch(console.error);
passportConfig();
app.use(cors({
origin : '*',//* 는 모두 허용, origin : true로 설정하면 * 대신 보낸 곳의 주소가 자동으로 들어가 편라함
credentials : false,//나중에 true 로 설정하기
}));//middleware
app.use(express.json());//위에 적기, front에서 json 형식으로 데이터 넘기면-> req.body에 제이슨 넣어줌
app.use(express.urlencoded({ extended : true }));//위에 적기, 폼 : urlencoded -> req.body에 넣어줌
//login 관련!
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(session());
app.use(passport.initialize());
app.use(passport.session({
saveUninitialized : false,
resave : false,
secret : process.env.COOKIE_SECRET,//secret 이 해킹당하면 데이터가 노출된다.
}));
app.use('/user',userRouter);
app.use('/post',postRouter);
app.listen(3065, ()=>{
console.log('실행중....')
});
.env
COOKIE_SECRET=nodebirdsecret
DB_PASSWORD=1234
config.js
const dotenv = require('dotenv');
dotenv.config();
module.exports={
"development": {
"username": "root",
"password": process.env.DB_PASSWORD,
"database": "react-nodebird",
"host": "127.0.0.1",
"dialect": "mysql"
},
"test": {
"username": "root",
"password": process.env.DB_PASSWORD,
"database": "react-nodebird",
"host": "127.0.0.1",
"dialect": "mysql"
},
"production": {
"username": "root",
"password": process.env.DB_PASSWORD,
"database": "react-nodebird",
"host": "127.0.0.1",
"dialect": "mysql"
}
}
위 : 쿠키 // 아래 : 내 데이터
www.passportjs.org/packages/passport-kakao/
middlewares.js
//login 검사용
exports.isLoggedIn = (req,res,next)=>{
if(req.isAuthenticated()){
next();//다음 미들웨어로 간다.
}else{
res.status(401).send('로그인 필요함')
}
}
exports.isNotLoggedIn = (req,res,next)=>{
if(!req.isAuthenticated()){
next();//다음 미들웨어로 간다. 즉, (req,res,next)으로 간다
}else{
res.status(401).send('로그인 필요함')
}
}
디비에 등록한 컬럼들을 모두 반영해서 프론트로 보내주지 않으면 발생하는 에러
아래와 같이 추가정보 더 적어서 보내주기
// PostForm -> reducer-> saga -> server -> saga(result.data 안에 들어있음)->reducer
router.post('/', isLoggedIn, async (req,res, next)=>{//POST /post
try {
const post = await Post.create({
content : req.body.content,
UserId : req.user.id,//누가 썼는지? 로그인 해서 알 수 있다. from passport deserializeUser
})
//디비 테이블열에 맞춰서 부족한 부분 채워넣기
//전체 정보 완성해서 프런트에 돌려주기
const fullPost = await Post.findOne({
where : {id : post.id},
include : [{
model : Image,
},{
model : Comment,
},{
mode : User,
}]
})
res.status(201).json(fullPost)//to front sagas
} catch (error) {
console.log(error);
next(error);
}
});
혹은 아래처럼
//login 전략
//return done(null, false, {reason : '존재하지 않음'})
//middleware 확장하는 방법
router.post('/login',isNotLoggedIn, (req,res,next)=> {
//2. saga에서 받은 데이터를 가지고 전략을 실행함
passport.authenticate('local', (err, user, info)=>{
//5. local 전략 완료 후 여기서 로긴 실행하기
if(err){
//서버에러
console.log(err);
return next(err);
}
if(info){
//client error
return res.status(403).send(info.reason);
}
//req.login 시 passport로 로그인 한다. -> 세션 로그인 저장해줌
//6. 여기서 passport login 한다 -> passport 실행은 index.js에서
return req.login(user, async (loginerr) => {
if(loginerr){
console.log(loginerr);
return next(loginErr);
}
const fullUserWithoutPassword = await User.findOne({
where : { id : user.id },
attributes : {
exclude : ['password']
},
include : [
{
model : Post, // hasMany라서 Post -> 복수형이 되어 Posts로 된다.
},
{
model : User,
as : 'Followings'
},
{
model : User,
as : 'Followers'
},
],
})
return res.status(200).json(fullUserWithoutPassword);
})
})(req,res,next);
});
쿠키확인 : connect.sid (connect & express session이 만든 쿠키!)
이 쿠키만 있으면 서버쪽에서 로긴이 된건지 안된건지 알 수 있다.
그런데 새로고침할때 이 만들어진 쿠키가 서버쪽으로 전달이 안되어서 로그인이 풀린것 처럼 보인거지 실제론 브라우저는 쿠키로 가지고 있기 때문에 언제든지 이 쿠키를 서버쪽으로 전달만 해주면 다시 로그인 상태로 돌아올 수 있다.
//사용자 불러오기
router.get('/', async (req,res,next)=>{//GET /user
try {
if(req.user){//즉, 로그인 된 상태에서 새로고침 될때만 불러오기
const fullUserWithoutPassword = await User.findOne({
where : { id : req.user.id },
attributes : {
exclude : ['password']
},
include : [
{
model : Post, // hasMany라서 Post -> 복수형이 되어 Posts로 된다.
attributes : ['id'],//개수만 불러올거라서..
},
{
model : User,
as : 'Followings',
attributes : ['id'],//개수만 불러올거라서..
},
{
model : User,
as : 'Followers',
attributes : ['id'],//개수만 불러올거라서..
},
],
})
res.status(200).json(fullUserWithoutPassword);
}else{
//logut 된 상태
res.status(200).json(null);
}
} catch (error) {
console.error(error);
next(error);
}
})
서버로부터 필요한 정보만 불러오기
include : [
{
model : Post, // hasMany라서 Post -> 복수형이 되어 Posts로 된다.
attributes : ['id'],//개수만 불러올거라서..
},
{
model : User,
as : 'Followings',
attributes : ['id'],//개수만 불러올거라서..
},
{
model : User,
as : 'Followers',
attributes : ['id'],//개수만 불러올거라서..
},
],
//uploads 파일 경로를 프론트와 공유하기 위해서 설정
app.use('/', express.static(path.join(__dirname, 'uploads')))// '/' 의미 : localhost:3065/
해쉬태그
'첫 번째 게시글 #해시태그 #익스프레스'.match(/#[^\s#]+/g);
<div>
{postData.split(/(#[^\s#]+)/g).map((v, i)=>{
//반복문 + #일치
//반복문이 바뀌지 않을때 index 키로 사용해도 된다.
if(v.match(/#[^\s#]+/g)){//#모양인 애들만 링크
return <Link href={`/hashtag/${v.slice(1)}`} key={i}><a>{v}</a></Link>
}
return v;//모든 글자 출력
})}
</div>
if(hashtags){
//tag.slice(1) : hashtag 때는것 + 소문자 저장
//findOrCreate : 없을때만 등록되고 있을때는 가져오는걸로 바뀜 -> 나중에 태그 검색서비스 때문
const result = await Promise.all(hashtags.map((tag)=>Hashtag.findOrCreate({
where : {name : tag.slice(1).toLowerCase()}
})));//findOrCreate result 결과 모양 : [[노드,true],[리액트, true]] -> 생성된건지 찾은건지..
await post.addHashtags(result.map((v)=>v[0]));
}
'React & Next.js' 카테고리의 다른 글
React & Mysql 트위터 만들기 - next.js & server side randering (0) | 2021.04.25 |
---|---|
useEffect & useCallback 정리 (0) | 2021.04.21 |
React & Mysql 트위터 만들기 - front 편, NEXTJS / REDUX / SAGA /dummy data 만들기 (0) | 2021.04.19 |
React 설명 & HOOK 설명 & Redux 설명 (상태관리 라이브러리) (0) | 2021.04.17 |
React - Cors Issue & Proxy 설정 (0) | 2021.04.17 |