Back-end/Node.js

[Node.js] 익스프레스로 SNS 서비스 만들기 (1)

poppy 2021. 7. 25. 12:13
반응형

익스프레스로 간단한 SNS 서비스를 만들어보겠습니다! 로그인, 이미지 업로드, 게시글 작성, 해시태그 검색, 팔로잉 기능이 들어갈 예정입니다 하나씩 차근차근 해보겠습니다 :)

 

1. 프로젝트 세팅하기

npm init 을 콘솔에 입력하여 package.json 파일을 만듭니다. 

npm init
{
  "name": "nodebird",
  "version": "0.0.1",
  "description": "익스프레스로 만드는 SNS 서비스",
  "main": "app.js",
  "scripts": {
    "start": "nodemon app"
  },
  "author": "sh",
  "license": "MIT"
}

 

데이터베이스 연결을 위해 시퀄라이즈를 사용할 것입니다. 시퀄라이즈를 설치하고 sequelize init 으로 프로젝트 기본 세팅을 합니다. 기본 세팅 후 필요한 패키지들을 설치합니다. 템플릿 엔진은 넌적스를 사용합니다.

npm install sequelize sequelize-cli mysql2
npx sequelize init
npm install express cookie-parser express-session morgan multer dotenv nunjucks
npm install -D nodemon

 

2. 필요한 파일 생성하기

app.js 와 .env 파일을 작성합니다. 현재 app.js 에는 라우터가 pageRouter만 있는 상태입니다. .env 는 비밀번호가 담겨있는 파일입니다. 비밀번호 같은 개인정보는 하드 코딩하지 않고 .env 에 저장하는 것이 좋습니다.

// app.js
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');

dotenv.config();
const pageRouter = require('./routes/page'); // 라우터 가져오기

const app = express();
app.set('port', process.env.PORT || 3000);
app.set('view engine', 'html');
nunjucks.configure('views', {
  express: app,
  watch: true,
});

app.use(morgan('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
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('/', 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'), '번 포트에서 대기중');
});
// .env
COOKIE_SECRET=nodebirdsecret

 

app.js 에 있던 pageRouter 파일을 생성하겠습니다. routes 폴더 생성 후 page.js 파일을 만듭니다. 이 라우터는 메인페이지로 세 가지 페이지로 이루어져 있습니다. GET /profile 은 내 정보 페이지이고, GET /join 은 회원가입 페이지이고, GET / 은 메인 페이지 입니다. 세 경로 모두 html 파일을 렌더링하여 페이지를 보여줍니다. 

 

router.use 는 라우터용 미들웨어로 템플릿 엔진(넌적스)에서 사용할 변수를 설정합니다. 모든 템플릿 엔진에서 공통으로 사용하기 위해 res.locals 로 설정합니다.

// routes/page.js
const express = require('express');

const router = express.Router();

// 템플릿 엔진에서 사용할 변수 설정
router.use((req, res, next) => {
  res.locals.user = null;
  res.locals.followerCount = 0;
  res.locals.followingCount = 0;
  res.locals.followerIdList = [];
  next();
});

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

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

// http://localhost:3000/
router.get('/', (req, res, next) => {
  const twits = [];
  res.render('main', {
    title: 'NodeBird',
    twits,
  });
});

module.exports = router;

 

3. 프론트 구성하기

프론트 부분 코드는 파일로 첨부하겠습니다. 서버 코드에 초점을 맞추고 있어서 프론트 코드는 따로 설명하지 않겠습니다. view 폴더 안에 layout.html, main.html, profile.html, join.html, error.html 을 생성합니다. public 폴더 안에 main.css 을 생성합니다.

layout.html
0.00MB
main.html
0.00MB
profile.html
0.00MB
join.html
0.00MB
error.html
0.00MB
main.css
0.00MB

 

여기까지 하면 프로젝트의 기본적인 세팅은 끝났고 콘솔에 npm start 입력 후  웹 페이지에 접속하면 다음과 같은 화면을 볼 수 있습니다!

 

 

4. 데이터베이스 세팅하기

데이터베이스는 MYSQL을 사용하겠습니다. 시퀄라이즈를 사용하기 위해 먼저 모델을 정의합니다. 모델을 정의할 때 모델 간의 관계도 정의해야 하므로 모델 간의 관계에 대해 먼저 살펴보겠습니다. User는 사용자 정보를 저장하는 모델, Post는 게시물을 저장하는 모델, Hashtag는 해시 태그 이름을 저장하는 모델입니다.

 

User : Post 1 : N (한 사용자가 여러 게시물을 작성할 수 있음)
User : User N : M (사용자들끼리 팔로잉할 수 있음)
Post : Hashtag N : M (게시물에 여러 해시태그가 붙음)

 

models 폴더 안에 user.js, post.js, hashtag.js, index.js 을 생성합니다. 

// models/user.js
const Sequelize = require('sequelize');

module.exports = class User extends Sequelize.Model {
  static init(sequelize) { // 테이블에 대한 설정
    return super.init({
      email: { // 이메일
        type: Sequelize.STRING(40),
        allowNull: true,
        unique: true,
      },
      nick: { // 닉네임
        type: Sequelize.STRING(15),
        allowNull: false,
      },
      password: { // 패스워드 
        type: Sequelize.STRING(100),
        allowNull: true,
      },
      provider: { // SNS 로그인한 경우에 사용하는 변수
        type: Sequelize.STRING(10),
        allowNull: false,
        defaultValue: 'local',
      },
      snsId: { // SNS 로그인한 경우에 사용하는 변수
        type: Sequelize.STRING(30),
        allowNull: true,
      },
    }, {
      sequelize,
      timestamps: true, // createdAt, updatedAt 자동 생성
      underscored: false,
      modelName: 'User',
      tableName: 'users',
      paranoid: true, // deletedAt 자동 생성
      charset: 'utf8',
      collate: 'utf8_general_ci',
    });
  }

  static associate(db) { // 테이블 간 관계 설정
    db.User.hasMany(db.Post); // 사용자와 게시물 관계
    // 사용자와 사용자 관계
    db.User.belongsToMany(db.User, { // 사용자가 여러 사용자를 팔로잉
      foreignKey: 'followingId',
      as: 'Followers',
      through: 'Follow',
    });
    db.User.belongsToMany(db.User, { // 사용자를 팔로잉하는 여러 명의 팔로워
      foreignKey: 'followerId',
      as: 'Followings',
      through: 'Follow',
    });
  }
};

User 모델과 Post 모델은 1:N 관계이므로 hasMany로 연결되어 있습니다. 팔로잉 기능은 User 모델과 User 모델이 N:M 관계를 가지므로 belongsToMany 로 연결되어 있습니다. N:M 관계에서는 모델들을 연결할 모델이 필요합니다. 그 모델을 정의해야 하는데 그것이 belongsToMany의 옵션입니다. through 는 모델들을 연결할 모델의 이름이고, foreignKey 는 모델에서 사용자 아이디를 저장하는 컬럼입니다. 둘 다 User 모델이기 때문에 아이디 컬럼명이 모두 UserId 이므로 foreignKey 로 따로 설정해야 합니다. as 는 foreignKey와 반대되는 모델을 의미합니다. 이렇게 관계를 설정하면 Followers - Follow - Followings 관계가 됩니다. 

 

// models/post.js
const Sequelize = require('sequelize');

module.exports = class Post extends Sequelize.Model {
  static init(sequelize) { // 테이블에 대한 설정
    return super.init({
      content: { // 게시글 내용
        type: Sequelize.STRING(140),
        allowNull: false,
      },
      img: { // 이미지 경로
        type: Sequelize.STRING(200),
        allowNull: true,
      },
    }, {
      sequelize,
      timestamps: true, // createdAt, updatedAt 자동 생성
      underscored: false,
      modelName: 'Post',
      tableName: 'posts',
      paranoid: false, 
      charset: 'utf8mb4',
      collate: 'utf8mb4_general_ci',
    });
  }

  static associate(db) { // 테이블 간 관계 설정
    db.Post.belongsTo(db.User);
    db.Post.belongsToMany(db.Hashtag, { through: 'PostHashtag' });
  }
};

User 모델과 Post 모델은 1:N 관계이므로 belongsTo로 연결되어 있습니다. Post 모델과 PostHashtag 모델은 N:M 관계이므로 belongsToMany 로 연결되어 있습니다. through 는 모델들을 연결할 모델의 이름입니다. 

 

// models/hashtag.js
const Sequelize = require('sequelize');

module.exports = class Hashtag extends Sequelize.Model {
  static init(sequelize) { // 테이블에 대한 설정
    return super.init({
      title: { // 해시 태그 이름
        type: Sequelize.STRING(15),
        allowNull: false,
        unique: true,
      },
    }, {
      sequelize,
      timestamps: true, // createdAt, updatedAt 자동 생성
      underscored: false,
      modelName: 'Hashtag',
      tableName: 'hashtags',
      paranoid: false,
      charset: 'utf8mb4',
      collate: 'utf8mb4_general_ci',
    });
  }

  static associate(db) { // 테이블 간 관계 설정
    db.Hashtag.belongsToMany(db.Post, { through: 'PostHashtag' });
  }
};

Post 모델과 Hashtag 모델이 N:M 관계이므로 belongsToMany로 연결되어 있습니다. through 는 모델들을 연결할 모델의 이름입니다. 

 

// models/index.js
const Sequelize = require('sequelize');
const env = process.env.NODE_ENV || 'development';
const config = require('../config/config')[env];
const User = require('./user');
const Post = require('./post');
const Hashtag = require('./hashtag');

const db = {};
const sequelize = new Sequelize(
  config.database, config.username, config.password, config,
);

db.sequelize = sequelize;
db.User = User;
db.Post = Post;
db.Hashtag = Hashtag;

User.init(sequelize);
Post.init(sequelize);
Hashtag.init(sequelize);

User.associate(db);
Post.associate(db);
Hashtag.associate(db);

module.exports = db;

위에서 생성한 모델들을 시퀄라이즈에 연결합니다. 각 모델들을 시퀄라이즈 객체에 연결합니다. 

 

데이터베이스와 연결할 정보를 수정합니다. config/config.json 파일을 자신의 데이터베이스에 맞게 수정하면 됩니다.

// config/config.json
{
  "development": {
    "username": "root",
    "password": "root의 비밀번호",
    "database": "nodebird",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
}

 

직접 MYSQL에 데이터베이스를 생성해도 되지만 다음 명령어를 입력하면 데이터베이스가 생성됩니다.

npx sequelize db:create

 

데이터베이스 생성 후 app.js 에 시퀄라이즈를 연결합니다.

// app.js
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');

dotenv.config();
const pageRouter = require('./routes/page');
const { sequelize } = require('./models'); // 시퀄라이즈 연결

// .. 생략
sequelize.sync({ force: false }) // 서버 실행 시 데이터베이스 연결
  .then(() => {
    console.log('데이터베이스 연결 성공');
  })
  .catch((err) => {
    console.error(err);
  });

// .. 생략

 

콘솔에 npm start 를 입력하면 콘솔에 다음과 같은 로그를 볼 수 있을 것 입니다!

 

 

반응형