// app.js
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import 'express-async-errors';
import tweetsRouter from './router/tweets.js';
import authRouter from './router/auth.js';
const app = express();
// 기본 미들웨어 설정
app.use(express.json());
app.use(cors());
app.use(helmet());
app.use(morgan('tiny'));
// Route(tweets)
app.use('/tweets', tweetsRouter);
// Route(Auth)
app.use('/auth', authRouter);
// 404 Error
app.use((req, res, next) => {
res.sendStatus(404);
});
// Error
app.use((error, req, res, next) => {
console.log(error);
res.status(500).send('Server Error');
});
app.listen(config.host.port);
// DataBase Connection
db.getConnection();
View
import express from 'express';
import 'express-async-errors';
import { body, param, validationResult } from 'express-validator';
import * as tweetController from '../controller/tweet.js';
import { validate } from '../middleware/validator.js';
import { isAuth } from '../middleware/auth.js';
const router = express.Router();
const validateTweet = [
body('text') // text : body 안에 포함되어 있는 req 값
.trim()
.isLength({ min: 3 })
.withMessage('text should be at least 3 characters'),
validate
];
// GET /tweets
// GET /tweets?username=:username
router.get('/', isAuth, tweetController.getTweets);
// GET /tweets/:id
router.get('/:id', isAuth, tweetController.getTweet);
// POST /tweets
router.post('/', isAuth, validateTweet, tweetController.createTweet);
// PUT /tweets/:id
router.put('/:id', isAuth, validateTweet, tweetController.updateTweet);
// DELETE /tweets/:id
router.delete('/:id', isAuth, tweetController.deleteTweet);
export default router;
Controller
import * as tweetRepository from '../data/tweet.js';
// tweets 다건 조회
export async function getTweets(req, res, next) {
const username = req.query.username;
const tweets = await (username
? tweetRepository.getAllByUsername(username)
: tweetRepository.getAll());
res.status(200).json(tweets);
}
// tweets 단건 조회
export async function getTweet(req, res, next) {
const id = req.params.id;
const tweet = await tweetRepository.getById(id);
if (!tweet) {
return res.status(404).json({message: `Tweet id(${id}) not found`});
}
res.status(200).json(tweet);
}
// tweet 생성
export async function createTweet(req, res, next) {
const { text } = req.body;
const tweet = await tweetRepository.create(text, req.userId);
res.status(201).json(tweet);
}
// tweet 수정
export async function updateTweet(req, res, next) {
const text = req.body.text;
const id = req.params.id;
const tweet = await tweetRepository.getById(id);
// 수정 대상 없음
if (!tweet) {
return res.status(404).json({message: `Tweet id(${id}) not found`});
}
// 다른 사용자 수정 방지
if (tweet.userId !== req.userId) {
return res.sendStatus(403);
}
const updated = await tweetRepository.update(text, id);
res.status(200).json(updated);
}
// tweet 삭제
export async function deleteTweet(req, res, next) {
const id = req.params.id;
const tweet = await tweetRepository.getById(id);
// 삭제 대상 없음
if (!tweet) {
return res.status(404).json({message: `Tweet id(${id}) not found`});
}
// 다른 사용자 삭제 방지
if (tweet.userId !== req.userId) {
return res.sendStatus(403);
}
const deleted = await tweetRepository.remove(id);
res.status(204).json(deleted);
}
Model (MySQL)
import { db } from '../db/database.js';
const SELECT_JOIN = 'SELECT T1.id AS id, T1.userId AS userId, T1.text AS text, T1.createdAt AS createdAt, T2.username AS username, T2.name AS name, T2.url AS url FROM dwitter.tweets T1 JOIN dwitter.users T2 ON T1.userId = T2.id';
const ORDER_DESC = 'ORDER BY CREATEDAT DESC';
// Auths 데이터와 Tweets 데이터 Join 헤서 출력 데이터 가공하기
// tweets 다건 조회
export async function getAll() {
return db
.execute(`${SELECT_JOIN} ${ORDER_DESC}`)
.then((result) => result[0]);
}
// tweets 다건 조회
export async function getAllByUsername(username) {
return db
.execute(`${SELECT_JOIN} WHERE T2.username = ? ${ORDER_DESC}`, [username])
.then((result) => result[0]);
}
// tweet 단건 조회
export async function getById(id) {
return db
.execute(`${SELECT_JOIN} WHERE T1.id = ?`, [id])
.then((result) => result[0][0]);
}
// tweet 생성
export async function create(text, userId) {
return db
.execute(`INSERT INTO dwitter.tweets (text, createdAt, userId) VALUES (?, ?, ?)`, [text, new Date(), userId])
.then((result) => getById(result[0].insertId));
}
// tweet 수정
export async function update(text, id) {
return db
.execute('UPDATE dwitter.tweets SET text = ? WHERE id = ?', [text, id])
.then(() => getById(id));
}
// tweet 삭제
export async function remove(id) {
return db
.execute('DELETE FROM dwitter.tweets WHERE id = ?', [id])
.then(() => getById(id));
}
Model (MySQL)
import MongoDB from 'mongodb';
import { getTweets } from '../db/mongo.js';
import * as userReository from '../data/auth_mongo.js';
// ---------------------------------
// [ MVC ( Model ) ]
// ---------------------------------
// - server 에서의 model
// - 데이터의 로직이 변경되어야 한다면 Model 에서만 변경해 주면 된다.
// - 데이터베이스
// 다건
// find() 는 커서(cursor) 형태로 데이터를 하나 하나씩 읽어온다.
// sort() 는 정렬 방식으로 해당 데이터에 양수면 오름차순, 음수면 내림차순.
export async function getAll() {
return getTweets()
.find()
.sort({ createdAt: -1 })
.toArray()
.then(mapTweets);
}
// 다건
export async function getAllByUsername(username) {
return getTweets()
.find({ username })
.sort({ createdAt: -1 })
.toArray()
.then(mapTweets);
}
// 단건
// 단건은 find() 가 아닌 findOne() 을 사용해야 한다.
export async function getById(id) {
return getTweets()
.findOne({ _id: new MongoDB.ObjectId(id) })
.then(mapOptionalTweet);
}
export async function create(text, userId) {
const { username, name, url } = await userReository.findById(userId);
const tweet = {
text,
createdAt: new Date(),
userId,
username,
name,
url
}
return getTweets()
.insertOne(tweet)
.then((data) => mapOptionalTweet({ ...tweet, _id: data.insertedId }));
}
// updateOne() : void
// -> 업데이트를 하고 아무 값도 받아오지 않는다면
// findOneAndUpdate() : object
// -> 업데이트를 하고 반환 값을 받아온다면
export async function update(text, id) {
return getTweets()
.findOneAndUpdate(
{ _id: new MongoDB.ObjectId(id) }, // 업데이트할 대상 선택
{ $set: { text } },
{ returnDocumnet: 'after' } // before: 업데이트 이전 값 반환, after: 업데이트 이후 값 반환
)
.then((result) => result.value)
.then(mapOptionalTweet);
}
export async function remove(id) {
return getTweets()
.deleteOne({ _id: new MongoDB.ObjectId(id) });
}
function mapOptionalTweet(tweet) {
return tweet ? { ...tweet, id: tweet._id.toString() } : tweet;
}
function mapTweets(tweets) {
return tweets.map(mapOptionalTweet);
}