Node.js/NodeJS-book

Express로 SNS 서비스 만들기 - 프로젝트 구조 갖추기

느리지만 꾸준하게 2022. 3. 15. 02:03
기능 : 로그인, 이미지 업로드, 게시글 작성, 해시태그 검색, 팔로잉

express-generator 대신 직접 구조를 갖춤
프런트엔드 코드(nunjucks로 만듬)보다 노드 라우터 중심으로 볼 것
관계형 데이터베이스 MySQL 선택

SNS 서비스에는 데이터베이스에 사용자 / 게시글 / 이미지 / 팔로잉 팔로워 관계 / 해시태그 이런 것들을 각각의 테이블로 만든다.

 

테이블들간에 관계가 있나? 한사람이 여러개의 해시태그  / 한 게시글은 여러개의 해시태그 / 하나의 해시태그도 그에 여러개 속한 여러개 개시글들이 있을 수 있다.

 

일대다 / 다대다 => 관계형 데이터베이스를 쓴다. MySQL 쓰자.

 

프로젝트 구조에 필요한 패키지 설치

npm i bcrypt cookie-parser dotenv express express-session morgan mysql2 nunjucks passport passport-local

 

package.json은 아래와 같다.

{
  "name": "nodebird",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "start": "nodemon app"
  },
  "author": "Jay",
  "license": "MIT",
  "dependencies": {
    "bcrypt": "^5.0.1",
    "cookie-parser": "^1.4.6",
    "dotenv": "^16.0.0",
    "express": "^4.17.3",
    "express-session": "^1.17.2",
    "morgan": "^1.10.0",
    "mysql2": "^2.3.3",
    "nunjucks": "^3.2.3",
    "passport": "^0.5.2",
    "passport-local": "^1.0.0"
  }
}

 

명령어 입력

 npm i sequelize sequelize-cli 
 
 // 후에

npx sequelize init
npm i multer

npm i -D nodemon

 

 

아래는 프로젝트 진행중인 error middleware를 뜻한다.

const express = require('express');
const cookieParser = require('cookie-parser');
const morgan = require('morgan');
const path = require('path');
const session = require('express-session');
const nunjucks = require('nunjucks');
const dotenv = require('dotenv');
const passport = require('passport');


dotenv.config();
const pageRouter = require('./routes/page');
const authRouter = require('./routes/auth');
const postRouter = require('./routes/post');
const userRouter = require('./routes/user');
const { sequelize } = require('./models');
const passportConfig = require('./passport');

const app = express();
passportConfig(); // 패스포트 설정
app.set('port', process.env.PORT || 8001);
app.set('view engine', 'html');
nunjucks.configure('views', {
  express: app,
  watch: true,
});
sequelize.sync({ force: false })
  .then(() => {
    console.log('데이터베이스 연결 성공');
  })
  .catch((err) => {
    console.error(err);
  });
passportConfig();

app.use(morgan('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.use('/img', express.static(path.join(__dirname, 'uploads')));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(session({
  resave: false,
  saveUninitialized: false,
  secret: process.env.COOKIE_SECRET,
  cookie: {
    httpOnly: true,
    secure: false,
  },
}));
app.use(passport.initialize());
app.use(passport.session());

app.use('/auth', authRouter);
app.use('/post', postRouter);
app.use('/user', userRouter);

app.use('/', pageRouter);

app.use((req, res, next) => {
  const error =  new Error(`${req.method} ${req.url} 라우터가 없습니다.`);
  error.status = 404;
  next(error);
});

app.use((err, req, res, next) => {
  res.locals.message = err.message;
  res.locals.error = process.env.NODE_ENV !== 'production' ? err : {};
  res.status(err.status || 500);
  res.render('error');
});

app.listen(app.get('port'), () => {
  console.log(app.get('port'), '번 포트에서 대기중');
});

 

app.js 에서 (next)error 부분은 에러 middleware를 뜻한다.

error를 처리하는 middleware 에서 next는 생략하면 안된다.(일반 middlewares는 next를 생략해도됨)

app.use('/', pageRouter);

app.use((req, res, next) => {
  const error =  new Error(`${req.method} ${req.url} 라우터가 없습니다.`);
  error.status = 404;
  next(error);
});

// error를 처리하는 middleware
app.use((err, req, res, next) => {
  res.locals.message = err.message;
  res.locals.error = process.env.NODE_ENV !== 'production' ? err : {};
  res.status(err.status || 500);
  res.render('error');
});

 

아래는 개발할 때만 error stack을 보여주고 배포모드 일때 error의 stack을 안보여주는 것을 뜻한다.

app.use((err, req, res, next) => {
  res.locals.message = err.message;
  res.locals.error = process.env.NODE_ENV !== 'production' ? err : {};

 

routes폴더내에 page.js 파일을 만들자. app.js를 보게되면 아래부분이 있는데

pageRouter부분앞에 그냥 / 이기 때문에

app.use('/', pageRouter);

 

page.js의 router 부분 앞에는 붙는게 없다.

router.get('/profile', isLoggedIn, (req, res) => {
  res.render('profile', { title: '내 정보 - NodeBird' });
});

router.get('/join', isNotLoggedIn, (req, res) => {
  res.render('join', { title: '회원가입 - NodeBird' });
});

아래에 twit 부분은 sns에 당장 게시할 부분이 없으므로 빈 배열을 넣어주었다.

// page.js

router.get('/', async (req, res, next) => {
  try {
    const posts = await Post.findAll({
      include: {
        model: User,
        attributes: ['id', 'nick'],
      },
      order: [['createdAt', 'DESC']],
    });
    const twits = [];
    res.render('main', {
      title: 'NodeBird',
      twits: posts,
      user: req.user,
    });
  } catch (err) {
    console.error(err);
    next(err);
  }
});

 

nunjucks로 html파일들을 만들자.

 

 

npm start를 하게 되면 main.css 파일이 없으므로 404에러가 나타나게 된다.

(npm start시 에러가 떠서 아래 블로그를 참고)

https://tristan91.tistory.com/529

 

TypeError: Router.use() requires a middleware function but got a Object

routes폴더의 파일 마지막에 const express = require('express'); const router = express.Router(); router.post('/', async (req, res, next) => { try { console.log(req); res.json(req); } catch (err) { co..

tristan91.tistory.com

 

 

<출처 조현영: Node.js 교과서 - 기본부터 프로젝트 실습까지 >

https://www.inflearn.com/course/%EB%85%B8%EB%93%9C-%EA%B5%90%EA%B3%BC%EC%84%9C/dashboard

 

[리뉴얼] Node.js 교과서 - 기본부터 프로젝트 실습까지 - 인프런 | 강의

노드가 무엇인지부터, 자바스크립트 최신 문법, 노드의 API, npm, 모듈 시스템, 데이터베이스, 테스팅 등을 배우고 5가지 실전 예제로 프로젝트를 만들어 나갑니다. 최종적으로 클라우드에 서비스

www.inflearn.com