반응형

# MySQL

1. MySQL 설치하기

### 데이터베이스란

  • 지금까지는 데이터를 서버 메모리에 저장했음. 그래서 서버 재시작하면 데이터도 사라짐. (영구적인 저장 공간 필요)
  • MYSQL 관계형 데이터베이스 사용.
  • 데이터베이스 : 관련성을 가지며 중복이 없는 데이터들의 집합
  • DBMS : 데이터베이스를 관리하는 시스템
  • RDBMS : 관계형 데이터베이스를 관리하는 시스템
  • 서버의 하드 디스크나 DDS 등 저장 매체에 데이터를 저장
  • 서버 종료 여부와 상관 없이 데이터를 계속 사용할 수 있음
  • 여러 사람이 동시에 접근할 수 있고, 권한을 따로 줄 수 있음

### 설치

 

MySQL :: Download MySQL Installer

Select Operating System: Select Operating System… Microsoft Windows Select OS Version: All Windows (x86, 32-bit) Windows (x86, 32-bit), MSI Installer 8.0.26 2.4M (mysql-installer-web-community-8.0.26.0.msi) MD5: eaddc383a742775a5b33a3783a4890fb | Signatu

dev.mysql.com

  • 설치 파일 실행 후, custom으로 체크하여 진행.
  • select products 에서 아래와 같이 2가지 항목 선택하여 넣기 (server, workbench)

  • Excute 눌러서 설치 진행 하고.
  • Accounts and Roles 에서 root 계정에 사용할 비밀번호 입력하여 계속 설치 진행.

### MySQL 접속

  • cmd (콘솔) 에서 MySQL이 설치된 경로로 이동
  • 기본 경로 : C:\Program Files\MySQL\MySQL Server 8.0\bin
  • -h는 호스트, -u는 사용자, -p는 비밀번호
mysql -h localhost -u root -p

Enter password : 설정한 비밀번호 입력.
  • 프롬프트가 mysql> 로 바뀌면 성공. (종료시에는 exit 입력)

### 워크벤치

  • mysql 이용하기 쉽게 도와주는 것 
  • 워크벤치 실행 후 MySQL Connections에서 local instance mysql 클릭 후 비밀번호 입력하여 실행.
  • 직접 커넥션 생성할 경우 Connection Name에 localhost 적고, 비밀번호 입력하여 진행.

 

2. 테이블 생성

### 데이터베이스 생성하기

  • 콘솔에서 MySQL 프롬프트 접속
CREATE SCHEMA `nodejs` DEFAULT CHARACTER SET utf8; 로 nodejs 데이터베이스 생성

use nodejs; 로 생성된 데이터베이스 선택.

### MySQL 프롬프트에서 테이블 생성

  • CREATE TABLE 데이터베이스명.테이블명] 으로 테이블 생성.
  • 사용자 정보를 저장하는 테이블
CREATE TABLE nodejs.users (
	id INT NOT NULL AUTO_INCREMENT,
    name VARCHAR(20) NOT NULL,
    age INT UNSIGNED NOT NULL,
    married TINYINT NOT NULL,
    comment TEXT NULL,
    created_at DATETIME NOT NULL DEFAULT now(),
    PRIMARY KEY(id),
    UNIQUE INDEX name_UNIQUE (name ASC))
    COMMENT = '사용자 정보'
    DEFAULT CHARACTER SET = utf8
    ENGINE=InnoDB;
  • 댓글 정보를 저장하는 테이블
CREATE TABLE nodejs.comments (
	id INT NOT NULL AUTO_INCREMENT,
    commenter INT NOT NULL,
    comment VARCHAR(100) NOT NULL,
    created_at DATETIME NOT NULL DEFAULT now(),
    PRIMARY KEY(id),
    INDEX commenter_idx (commenter ASC),
    CONSTRAINT commenter
    FOREIGN KEY (commenter)
    REFERENCES nodejs.users (id)
    ON DELETE CASCADE
    ON UPDATE CASCADE)
    COMMENT = '댓글'
    DEFAULT CHARSET=utf8mb4
    ENGINE=InnoDB;

 

3. 컬럼 옵션

## 다양한 컬럼 옵션들

  • id INT NOT NULL AUTO_INCREMENT 
컬럼명 옆의 것들은 컬럼에 대한 옵션들

INT : 정수 자료형 (FLOAT, DOUBLE은 실수)
VARCHAR : 문자열 자료형, 가변 길이 (CHAR은 고정 길이)
TEXT : 긴 문자열은 TEXT로 별도 저장
DATETIME : 날짜 자료형 저장
TINYINT : -128 ~ 127 까지 저장

NOT NULL : 빈 값은 받지 않는다는 뜻(NULL은 빈 값 허용)
AUTO_INCREMENT : 숫자 자료형인 경우 다음 로우가 저장될 때 자동으로 1증가
UNSIGNED : 0과 양수만 허용
ZEROFILL : 숫자의 자리 수가 고정된 경우 빈 자리에 0을 넣음
DEFAULT now() : 날짜 컬럼의 기본값을 현재 시간으로.
  • PRIMARY KEY (id)
id가 테이블에서 로우를 특정할 수 있게 해주는 고유한 값임을 의미
학번, 주민등록번호같은 개념
  • UNIQUE INDEX name_UNIQUE (name ASC)
해당 컬럼(name)이 고유해야 함을 나타내는 옵션
name_UNIQUE는 이 옵션의 이름 (다른 이름으로 지어도 됨)
ASC는 인덱스를 오름차수능로 저장함을 의미 (DESC : 내림차순)

### 테이블 생성확인

  • 테이블 생성확인 : DESC 테이블명; 입력하여 생성확인. (예 : DESC users;)
  • 테이블 삭제하기 : DROP TABLE 테이블명 (예 : DROP TABLE users;)
  • 테이블 목록확인 : SHOW TABLES;

 

4. CRUD 작업

### CRUD

  • CRUD 란? Create / Read / Update / Delete의 두문자어, 데이터베이스에서 가장 많이 하는 작업 4가지.

### Create

  • INSERT INTO 테이블 (컬럼명들) VALUES (값들)
INSERT INTO nodejs.users (name, age, married, comment) VALUES ('zero', 24, 0, '자기소개1');

INSERT INTO nodejs.users (name, age, married, comment) VALUES ('nero', 32, 1, '자기소개2');

### Read

  • SELECT 컬럼 FROM 테이블명
SELECT * FROM nodejs.users;
  • 컬럼만 따로 추리는 것 가능.
SELECT name, married FROM nodejs.users;
  • Read 조건절 : WHERE 로 조건을 주어 선택 가능.
1. AND
AND로 여러가지 조건을 동시에 만족하는 것을 찾음.

SELECT ma,e. age FROM nodejs.users WHERE married = 1 AND age > 30;


2. OR
OR로 여러가지 조건 중 하나 이상을 만족하는 것을 찾음.
SELECT id, name FROM nodejs.users WHERE married = 0 OR age > 30;
  • 정렬해서 찾기 : ORDER BY로 특정 컬럼 값 순서대로 정렬 가능. (ASC 기본)
DESC (내림차순) / ASC (오름차순)
SELECT id, name FROM nodejs.users ORDER BY age DESC;
  • LIMIT 으로 조회할 개수 제한
SELECT id, name FROM nodejs.users ORDER BY age DESC LIMIT 1;
  • OFFSET으로 앞의 로우들 스킵 가능 (OFFSET 2면 세 번째 것부터 찾음.)
SELECT id, name FROM nodejs.users ORDER BY age DESC LIMIT 1 OFFSET 1;

### Update

  • 데이터베이스에 있는 데이터를 수정하는 작업.
  • Update 테이블명 SET 컬럼 = 새값 WHERE 조건
UPDATE nodejs.users SET comment = '바꿀 내용' WHERE id = 2;

### Delete

  • 데이터베이스에 있는 데이터를 삭제하는 작업.
  • DELETE FROM 테이블명 WHERE 조건.
DELETE FROM nodejs.users WHERE id = 2;

 

5. 시퀄라이즈 사용하기

### 시퀄라이즈 ORM

  • MySQL 작업을 쉽게 할 수 있도록 도와주는 라이브러리
  • ORM : Object Relational Mapping으로 객체와 데이터를 매핑(1대1 짝지음)
  • MySQL 외에도 다른 RDB(Maria, Postgre, SQLite, MSSQL)와 호환됨
  • 자바스크립트 문법으로 데이터 베이스 조작 가능
  • 시퀄라이즈 예제 : https://github.com/zerocho/nodejs-book/tree/master/ch7/7.6/learn-sequelize 
 

GitHub - ZeroCho/nodejs-book

Contribute to ZeroCho/nodejs-book development by creating an account on GitHub.

github.com

### 시퀄라이즈 CLI 사용하기

  • 시퀄라이즈 명령어 사용하기 위해 sequelize-cli 설치
  • mysql2는 MySQL DB가 아닌 드라이버 (Node.js와 MySQL을 이어주는 역할)
npm i express morgan nunjucks sequelize sequelize-cli mysql2

npm i -D nodemon
  • npx sequelize init으로 시퀄라이즈 구조 생성
npx sequelize init

### models / index.js 수정

  • require*../config/config) 설정 로딩
  • new Sequelize(옵션들...)로 DB와 연결 가능.

### mysql 연결하기

  • app.js 작성, sequelize.sync로 연결
const express = require('express');
const path = require('path');
const morgan = require('morgan');
const nunjucks = require('nunjucks');

const { sequelize } = require('./models');
const indexRouter = require('./routes');
const usersRouter = require('./routes/users');
const commentsRouter = require('./routes/comments');

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

app.use(morgan('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));

app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/comments', commentsRouter);

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'), '번 포트에서 대기 중');
});

 

반응형
반응형

# SNS 서비스 만들기

1. 노드버드 프로젝트 구조 갖추기.

### NodeBird SNS 서비스

  • 기능 : 로그인, 이미지 업로드, 게시글 작성, 해시태그 검색, 팔로잉
  • express-generator 대신 직접 구조를 갖춤
  • 프런트엔드 코드보다 노드 라우터 중심으로 볼 것.
  • 관계형 데이터 베이스 MySQL 선택.

### 프로젝트 시작

  • 프로젝트 폴더 생성.
  • package.json 파일 생성 : 프로젝트의 기본.
npm init
  • 시퀄라이즈 폴더 구조 생성
npm i sequelize mysql2 sequelize-cli

npx sequelize init

### 폴더 구조 설정

  • views (템플릿 엔진), routes (라우터), public (정적 파일 : css 등), passport (패스포트) 폴더 생성
  • app.js와 .env 파일도 생성하기.

### 패키지 설치와 nodemon

  • npm 패키지 설치 후 nodemon도 설치
  • nodemon은 서버 코드가 변경되었을때 자동으로 서버를 재시작해줌.
npm i -D nodemon

pm i express express-session nunjucks morgan cookie-parser dotenv multer

### 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 app = express();
app.set('port', process.env.PORT || 8001);
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=cookiesecret

### 라우터 생성

  • routes/page.js : 템플릿 엔진을 렌더링하는 라우터
  • views/layout.html : 프론트 엔드 화면 레이아웃 (로그인/유저 정보 화면)
  • views/main.html : 메인 화면 (게시글들이 보임)
  • views/profile.html : 프로필 화면 (팔로잉 관계가 보임)
  • views/error.html : 에러 발생 시 에러가 표시될 화면
  • public/main.css : 화면 css
  • 소스 참고 : https://github.com/ZeroCho/nodejs-book/tree/master/ch9/9.1/nodebird
 

GitHub - ZeroCho/nodejs-book

Contribute to ZeroCho/nodejs-book development by creating an account on GitHub.

github.com

  • 각 파일 생성 완료 후 npm start로 실행. 

 

2. 데이터베이스 구조 갖추기

### 모델 생성

  • models/user.js : 사용자 테이블과 연결됨.
provider : 카카오 로그인인 경우 kakao, 로컬 로그인(이메일/비밀번호)인 경우 local

snsId : 카카오 로그인인 경우 주어지는 id
  • models/post.js : 게시글 내용과 이미지 경로를 저장 (이미지는 파일로 저장)
  • models/hashtag.js : 해시태그 이름을 저장 (나중에 태그로 검색하기 위함)

 

3. 테이블 관계 정의하기

### models/index.js

  • 시퀄라이즈가 자동으로 생성해주는 코드 대신 다음과 같이 변경.
모델들을 불러옴 (require)
모델 간 관계가 있는 경우 관계 설정
User(1) : Post(다)
Post(다) : Hashtag(다)
User(다) : User(다)

### associate 작성하기

  • 모델간의 관계들 associate에 작성
1 대 다 : hasMany와 belongsTo

다 대 다 : belongsToMany
	- foreignKey : 외래키
	- as : 컬럼에 대한 별명
	- through : 중간 테이블

### 팔로잉-팔로워 다대다 관계

  • User(다) : User(다)
  • 다대다 관계이므로 중간 테이블(Follow) 생성됨.
  • 모델 이름이 같으므로 구분 필요함(as가 구분자 역할, foreignKey는 반대 테이블 컬럼의 프라이머리 키 컬럼)
  • 시퀄라이즈는 as 이름을 바탕으로 자동으로 addFollower, getFollowers, addFollowing, getFollowings 메서드 생성

### 시퀄라이즈 설정하기

  • 시퀄라이즈 설정은 config/config.json 에서, 개발 환경용 설정은 development 아래에.
{
  "development": {
    "username": "root",
    "password": "root 비밀번호",
    "database": "nodebird",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  • 설정 파일 작성 후 nodebird 데이터베이스 생성.
npx sequelize db:create

### 모델과 서버 연결하기

  • npm start로 서버 실행 시 콘솔에 SQL문이 표시됨.
npm start

> nodebird@1.0.0 start C:\study\study_1\project_sns_service
> nodemon app

[nodemon] 2.0.13
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json  
[nodemon] starting `node app.js`
8001 번 포트에서 대기중
Executing (default): CREATE TABLE IF NOT EXISTS `users` (`id` INTEGER NOT NULL auto_increment , `email` VARCHAR(40) UNIQUE, `nick` VARCHAR(15) NOT NULL, `password` VARCHAR(100), `provider` VARCHAR(10) NOT NULL DEFAULT 'local', `snsId` VARCHAR(30), `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, `deletedAt` DATETIME, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_general_ci;
Executing (default): SHOW INDEX FROM `users` FROM `nodebird`
Executing (default): CREATE TABLE IF NOT EXISTS `posts` (`id` INTEGER NOT NULL auto_increment , `content` VARCHAR(140) NOT NULL, `img` VARCHAR(200), `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, `UserId` INTEGER, PRIMARY KEY (`id`), FOREIGN KEY (`UserId`) 
REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;       
Executing (default): SHOW INDEX FROM `posts` FROM `nodebird`
Executing (default): CREATE TABLE IF NOT EXISTS `hashtags` (`id` INTEGER NOT NULL auto_increment , `title` VARCHAR(15) NOT NULL UNIQUE, 
`createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;
Executing (default): SHOW INDEX FROM `hashtags` FROM `nodebird`
Executing (default): CREATE TABLE IF NOT EXISTS `Follow` (`createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, `followingId` INTEGER , `followerId` INTEGER , PRIMARY KEY (`followingId`, `followerId`), FOREIGN KEY (`followingId`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (`followerId`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_general_ci;
Executing (default): SHOW INDEX FROM `Follow` FROM `nodebird`
Executing (default): CREATE TABLE IF NOT EXISTS `PostHashtag` (`createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, `PostId` INTEGER , `HashtagId` INTEGER , PRIMARY KEY (`PostId`, `HashtagId`), FOREIGN KEY (`PostId`) REFERENCES `posts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (`HashtagId`) REFERENCES `hashtags` (`id`) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;
Executing (default): SHOW INDEX FROM `PostHashtag` FROM `nodebird`
데이터베이스 연결 성공

 

4. 패스포트 사용하기

### 패스포트 설치

  • 로그인 과정을 쉽게 처리할 수 있게 도와주는 Passport 설치하기
  • 비밀번호 해시화를 위한 bcrypt도 같이 설치
  • 설치 후 app.js와 연결
  • passport.session(): req.session 객체에 passport 정보를 저장
  • express-session 미들웨어에 의존하므로 이보다 더 뒤에 위치해야 함.
npm i passport passport-local passport-kakao bcrypt

### 패스포트 모듈 작성

  • passport/index.js 작성
  • passport.serializeUser : req.session 객체에 어떤 데이터를 저장할 지 선택, 사용자 정보를 다 들고 있으면 메모리를 많이 차지하기 때문에 사용자의 아이디만 저장
  • passport.deserializeUser : req.session에 저장된 사용자 아이디를 바탕으로 DB 조회로 사용자 정보를 얻어낸 후 req.user에 저장.
const passport = require('passport');
const local = require('./localStrategy');
const kakao = require('./kakaoStrategy');
const User = require('../models/user');

// passport는 전략을 사용(로그인을 어떻게 할지 적어놓은 파일.)
module.exports = () => {

    // 메모리의 효율성을 위해 serializeUser, deserializeUser 사용
    // auth.js 에서 login 라우터 에서 req login에 넣은 유저가 넘어와서
    passport.serializeUser((user, done) => {
        done(null, user.id);    // 유저에서 user의 id만 뽑아서 done 수행. -> 세션의 유저 id만 저장하는 것. (user 저장가능하지만, 서버 메모리가 한정적이므로 id만 저장./ 실무에서는 메모리에도 저장하면 안됨_실무에서 이러면 컴퓨터가 못버팀)
    });

    // 메모리에 저장된 id로 user.findOne 해서 전체 유저를 복구해줌.
    passport.deserializeUser((id, done) => {
        User.findOne({ where: { id } })
            .then(user => done(null, user)) // req.user로 접근할 수 있게됨.(로그인한 사용자 정보 확인가능)
            .catch(err => done(err));
    });

    local();
    kakao();
};

### 패스포트 처리 과정 (중요)

  • 로그인 과정
1. 로그인 요청이 들어옴
2. passport.authenticate 메서드 호출
3. 로그인 전략 수행
4. 로그인 성공 시 사용자 정보 객체와 함께 req.login 호출
5. req.login 메서드가 passport.serializeUser 호출
6. req.session에 사용자 아이디만 저장
7. 로그인 완료
  • 로그인 이후 과정
1. 모든 요청에 passport.session() 미들웨어가 passport.deserializeUser 메서드 호출
2. req.session에 저장된 아이디로 데이터베이스에서 사용자 조회
3. 조회된 사용자 정보를 req.user에 저장
4. 라우터에서 req.user 객체 사용 가능

### 로컬 로그인 구현

  • passport-local 패키지 필요
  • 로컬 로그인 전략 수립
  • 로그인에만 해당하는 전략이므로 회원가입은 따로 만들어야 한다.
  • 사용자가 로그인 했는지, 하지 않았는지 여부를 체크하는 미들웨어도 만듦.
  • routes/middlewares.js
// 로그인 한지 여부
exports.isLoggedIn = (req, res, next) => {
    if (req.isAuthenticated()) {
        next();
    } else {
        res.status(403).send('로그인 필요');
    }
};

// 로그인 안 한지 여부
exports.isNotLoggedIn = (req, res, next) => {
    if (!req.isAuthenticated()) {
        next();
    } else {
        const message = encodeURIComponent('로그인한 상태입니다.');
        res.redirect(`/?error=${message}`);
    }
};
  • routes/page.js
const { application } = require('express');
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();
});

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

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

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

module.exports = router;

### 회원가입 라우터

  • routes/auth.js 작성
  • bcrypt.hash로 비밀번호 암호화
  • hash의 두 번째 인수는 암호화 라운드
  • 라운드가 높을수록 안전하지만, 오래 걸림
  • 적당한 라운드를 찾는 게 좋다.
  • ?error 쿼리스트링으로 1회성 메시지
  • routes/auth.js
const express = require('express');
const passport = require('passport');
const bcrypt = require('bcrypt');
const { isLoggedIn, isNotLoggedIn } = require('./middlewares');
const User = require('../models/user');

const router = express.Router();

// 회원가입_ 로그인 안 한 사람만 회원가입 할 수 있도록 isNotLoggedIn
router.post('/join', isNotLoggedIn, async (req, res, next) => {
    const { email, nick, password } = req.body;

    try {
        // 먼저 기존에 입력된 이메일로 가입된 대상 있는지 검사.
        const exUser = await User.findOne({ where : { email } });   

        // 입력된 이메일로 가입된 대상이 존재하는 경우.
        if (exUser) {
            return res.redirect('/join?error=exist');   // 쿼리스트링사용 프론트로 exist 날려서 프론트에서 처리될 수 있도록 함.
        }

        // 입력된 이메일로 가입된 대상 없는경우.
        const hash = await bcrypt.hash(password, 12);   // hash(값, 12)에서 12는 얼마나 복잡하게 해시할건지 나타냄. (높을수록 복잡함, 소요시간 오래걸림)

        // 유저 생성.
        await User.create({
            email,
            nick,
            password: hash,
        });

        return res.redirect('/');
    } catch (error) {
        console.error(error);
        return next(error);
    }
});

// 로그인 로그인 안 한 사람만 로그인 할 수 있도록 isNotLoggedIn
router.post('/login', isNotLoggedIn, (req, res, next) => {
    // 미들웨어를 확장하는 패턴.
    // login 시 아이디 비밀번호 입력하면 passport.authenticate('local', 부분 실행 -> localStrategy.js 로 가게됨.
    passport.authenticate('local', (authError, user, info) => {
        
        // 서버에 에러있는 경우.
        if (authError) {
            console.error(authError);
            return next(authError);
        }

        // 로그인 실패한 경우
        if (!user) {
            return res.redirect(`/?loginError=${info.message}`);    // 로그인 실패에 관한 메시지를 담아서 프론트로 전달.
        }

        // 로그인 성공한 경우 이때 passport의 index.js로 가게됨.
        return req.logIn(user, (loginError) => {
            if (loginError) {
                console.error(loginError);
                return next(loginError);
            }

            return res.redirect('/');
        });
    })(req, res, next); // 미들웨어 내의 미들웨어에는 (req, res, next)를 붙임.
});

// 로그아웃 로그인 한 사람만 로그아웃 할 수 있도록 isLoggedIn
router.get('/logout', isLoggedIn, (req, res) => {
    req.logout();
    req.session.destroy();
    res.redirect('/');
});

// kakao로 로그인 하기 클릭 authenticate('kakao') -> kakaoStrategy로 가게됨 그래서 카카오 로그인을 하게됨. (kakao 사이트로 가게됨)
router.get('/kakao', passport.authenticate('kakao'));

// kakao 로그인 성공 시 kakao에서 /kakao/callback로 요청을 쏴줌. -> passport.authenticate('kakao', 실행 -> kakaoStrategy로로 가서 검사진행.
router.get('/kakao/callback', passport.authenticate('kakao', {
    failureRedirect: '/',   // 실패 시 여기로.
}), (req, res) => {         // 성공 시 여기 실행.
    res.redirect('/');
});

module.exports = router;

### 로그인 라우터

  • routes/auth.js 작성
  • passport.authenticate('local'); 로컬 전략
  • 전략을 수행하고 나면 authenticate의 콜백 함수 호출됨
  • authError : 인증 과정 중 에러.
  • user : 인증 성공 시 유저 정보
  • info : 인증 오류에 대한 메시지
  • 인증이 성공했다면 req.login으로 세션에 유저 정보 저장.

### 로컬 전략 작성

  • passport/localStrategy.js 작성
  • usernameField의 passwordField가 input 태그의 name(body-parser의 req.body)
  • 사용자가 DB에 저장되어있는지 확인 후 있다면 비밀번호 비교(bcrypt.compare)
  • 비밀번호까지 일치하면 로그인.
  • passport/localStrategy.js
const passport = require('passport');
const localStrategy = require('passport-local');
const bcrypt = require('bcrypt');

const User = require('../models/user');

module.exports = () => {
    passport.use(new localStrategy({
        usernameField: 'email',     // req.body.email
        passwordField: 'password',  // req.body.password
    }, async (email, password, done) => {
        try {
            // 로그인 시 입력된 이메일을 가진 대상이 존재하는지 확인.
            const exUser = await User.findOne({ where : { email } });

            // 입력된 이메일 대상이 존재하는 경우.
            if (exUser) {

                // 비밀번호 비교(입력된 비밀번호와 해시화된 비밀번호 비교)
                const result = await bcrypt.compare(password, exUser.password);
                if (result) {
                    done(null, exUser); // done의 경우 인수를 3개 받음. 여기서 1번 : 서버 / 2번 : 로그인가능여부 / 3번 로그인실패시 메시지
                } else {
                    done(null, false, { message: '비밀번호가 일치하지 않습니다.' });
                }
            // 입력된 이메일 대상이 존재하지 않는 경우.
            } else {
                done(null, false, { message: '가입되지 않은 회원입니다.' });
            }
        } catch (error) {
            console.error(error);
            done(error);
        }
    }));
};

 

6. 카카오 로그인하기

### 카카오 로그인용 라우터 만들기

  • 회원가입과 로그인이 전략에서 동시에 수행.
  • passport.authenticate('kakao')만 하면 됨
  • /kakao/callback 라우터에서는 인증 성공 시 (res.redirect)와 실패 시(failureRedirect) 리타이렉트할 경로를 지정.

### 카카오 로그인 앱 만들기

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

  • 내 애플리케이션에서 애플리케이션 추가하기 진행.
  • 생성된 항목 클릭하여 진입 후 플랫폼으로 이동. Web 플랫폼 등록.
  • 아래와 같이 로컬 등록, 추후 실제 도메인 있는 경우 해당 도메인 등록 (http://localhost:8081)

  • 카카오 로그인 항목으로 이동. 활성화 진행. 

  • 밑에 Redirect URI 등록 클릭 후 로컬 등록. (http://localhost:8001/auth/kakao/callback)

  • 동의항목으로 이동. 받을것들 선택 진행. (닉네임, 프로필 사진, 이메일 등)

  • 앱 키로 이동. REST API 키 복사 진행 후 .env에 아래와 같이 추가.
KAKAO_ID=복사한 REST_API 키 등록

 

반응형
반응형

# 몽고디비

1. NoSQL

  • MySQL 같은 SQL 데이터베이스와는 다른 유형의 데이터
  • NoSQL의 대표주자인 mongoDB (몽고디비) 사용.
SQL 과 NoSQL 비교
SQL (MySQL) NoSQL (몽고디비)
규칙에 맞는 데이터 입력 자유로운 데이터 입력
테이블 간 JOIN 지원 컬렉션 간 JOIN 미지원
안정성, 일관성 확장성, 가용성
용어 (테이블, 로우, 컬럼) 용어 (컬렉션, 다큐먼트, 필드)
  • JOIN : 관계가 있는 테이블끼리 데이터를 합치는 기능 (몽고디비 aggregate로 흉내 가능)
  • 빅데이터, 메시징, 세션 관리 등 (비정형 데이터)에는 몽고디비 사용하면 좋음.

 

2. 몽고디비 설치

### 윈도의 경우.

 

MongoDB Community Download

Download the Community version of MongoDB's non-relational database server from MongoDB's download center.

www.mongodb.com

  • community 버전 다운로드하여 설치 진행, 설치 시 complete 클릭 후 Install MongD as a Service 체크 해제 이후에 Install MongoDB Compass는 체크하여 설치 진행.

 

3. 몽고디비 연결

  • 윈도의 경우 C:\에 data 폴더를 만들고 그 안에 db 폴더 생성.
  • 콘솔로 몽고디비가 설치된 경로(기본적으로 C:\Program Files\MongoDB\Server\5.0\bin)로 이동하여 몽고디비를 실행. 그리고 콘솔에 아래와 같이 이력하여 실행
# 콘솔
mongod
  • mongd로 실행 후 그대로 놔둬야 몽고디비 서버가 돌아감. (만약 실행해둔걸 끄게되면 몽고디비 꺼짐)
  • mongd가 실행된 상태에서 mongo.exe 실행 시 몽고디비 사용 가능.

 

4. 어드민 설정

  • 어드민 권한을 설정하여 디비에 비밀번호 걸기
# 콘솔
use admin

db.createUser({ user: '이름'. pwd: '비밀번호', roles: ['root']});
  • mongod를 입력했던 콘솔을 종료한 후 mongod --auth 명령어로 접속. (--auth는 로그인이 필요하다는 의미.)
  • mongo를 입력한 콘솔도 종료한 후 mongo admin -u 이름 -p 비밀번호로 접속.

 

5. 커넥션 생성하기

  • 컴퍼스 (MongDB Compass Community)로 접속 (mongod 실행된 상태에서 진행)
  • Fill in connection fields individually 클릭 후 아래와 같이 입력 후 connect 진행

  • 아래는 접속 성공 화면 (기본적으로 admin / config / local 존재)

 

6. 데이터 베이스 생성

### 터미널 이용 시.

  • use 데이터베이스명으로 생성
# 콘솔
use nodejs	// nodejs 이름의 데이터베이스 생성.
  • show dbs로 목록 확인.
# 콘솔
show dbs

출력 예
admin   0.000GB
config  0.000GB
local   0.000GB

=> 방금 생성한 nodejs는 보이지 않음. (데이터 넣기 전까지는 보이지 않음)
  • db로 현재 사용중인 데이터베이스 확인 가능.
# 콘솔
db

출력 예
nodejs

### MongoDB Compass 이용 시.

  • CREATE DATABASE 클릭하여 생성

 

7. 컬렉션 생성 (=SQL에서의 테이블)

  • 따로 생성할 필요는 없음.
  • 다큐먼트(=로우)를 넣는 순간 컬렉션도 자동 생성된다.
  • 직접 생성하는 명령어도 있다. (아래참고)
# 콘솔
db.createCollection('users')
{ "ok" : 1 }

db.createCollection('comments')
{ "ok" : 1 }
  • show collections로 현재 컬렉션 확인 가능.
# 콘솔
show collections

출력 예
comments
users
  • 컬렉션 생성 후 MongoDB Compass에서 새로고침 클릭 시 생긴 걸 확인 가능.

 

8. 몽고디비 CRUD 작업

### Create

  • 몽고디비는 컬럼을 정의하지 않아도 된다.
  • 자유로움이 장점, 무엇이 들어올지 모른다는 단점 (몽고디비는 오타에 취약하므로 필드명에 주의.)
  • 자바스크립트의 자료형을 따른다. (차이점도 존재)
  • ObjectId : 몽고 디비의 자료형으로 고유 아이디 역할. ( _id : ObjectId(" ") )
  • save method로 저장
# 콘솔
use nodejs

db.users.save({ name: 'zero', age: 28, married: false, comment: 'hello hi.', createAt: new Date() });

db.users.save({ name: 'mero', age: 32, married: true, comment: 'what hi hello.', createAt: new Date() });
  • MongoDB Compass 로 생성할 경우 : users 컬렉션 클릭 후 ADD DATA 클릭 > Insert Document 클릭하여 json 형식에 맞게 작성 or 한줄씩 추가(VIEW 옆에 { } or 목록모양 클릭)하여 생성.

### Create (관계 설정)

  • 컬렉션 간 관계를 강요하는 제한이 없으므로 직접 ObjectId를 넣어 연결.
  • 사용자의 ObjectId를 찾은 뒤 댓글 컬렉션에 넣음.
# 콘솔
db.users.find({ name : 'zero' }, { _id : 1 })

=>
첫 번째 객체가 find 하는 조건. ({ name : 'zero' })
두 번째 객체는 어떤 필드를 보여줄지. ({ _id : 1 })	// 1 : 보여줌, 0 : 숨김
  • users에서 댓글 작성자의 ObjectId를 comments에서 commenter에 넣어주면 유저가 연결되는 것임. (단, 몽고디비가 commenter에 대해 존재하는지 여부 등을 검사해 주지 않음. 그래서 오타에 주의해야 함)

### Read

  • find로 조회, findOne으로 하나만 조회
# 콘솔
db.users.find({});	// users 컬렉션 전체 조회.

db.comments.find({});	// comments 컬렉션 전체 조회.

### Read (조건)

  • 두 번째 인수로 조회할 필드를 선택할 수 있다. (1은 추가 / 0은 제외) ( _id는 기본적으로 1임)
# 콘솔
db.users.find({ _id: 0, name: 1, married: 1 });
  • 첫 번째 인수로 조회 조건을 입력할 수 있다. ($gt 나 $or 같은 조건 연산자 사용)
# 콘솔
db.users.find({ $or: [{ age: { $gt: 30 }, { married: false }] }, { _id: 0, name: 1, age: 1 });
  • 정렬은 sort 메서드로 함. (1 : 오름차순 / -1 : 내림차순)
# 콘솔
db.users.find({}, { _id: 0, name: 1, age: 1}).sort({ age: -1 });
  • limit 메서드로 조회할 다큐먼트 개수 제한
# 콘솔
db.users.find({}, { _id: 0, name: 1, age: 1 }).sort({ age: -1 }).limit(1);
  • skip 메서드로 건너뛸 다큐먼트 개수 제공
# 콘솔
db.users.find({}, { _id: 0, name: 1, age: 1 }).sort({ age: -1 }).limit(1).skip(1);

### Update

  • update 메서드로 쿼리.
  • 첫 번째 인수로 수정 대상을, 두 번째 인수로 수정 내용을 제공
  • $set을 붙이지 않으면 다큐먼트 '전체'가 대체되므로 주의
# 콘솔
db.users.update({ name: 'nero' }, { $set: { comment: '안녕하세요. 해당 필드 수정' } });
  • 결과로 수정된 개수가 나옴.

### Delete

  • remove 메서드로 쿼리.
  • 첫 번째 인수로 삭제할 대상 조건 제공
# 콘솔
db.users.remove({ name: 'nero' });
  • 성공 시 삭제된 개수가 반환 됨.

 

9. 몽구스 ODM

  • 몽고디비 작업을 쉽게 할 수 있도록 도와주는 라이브러리. (몽고디비 드라이버도 내장되어 있음)
  • ODM : Object Document Mapping로 객체와 도큐먼트를 매핑(1대 1 짝지음)
  • 몽고디비에 없어 불편한 기능들을 몽구스가 보완.
  • 테이블과 유사한 기능, JOIN 기능 추가.
  • $set 등 빼먹는 것 방지, 쿼리빌더(쿼리를 쉽게 만들 수 있게 해주는 기능) 기능 제공.
  • 몽구스 예제 참고 : https://github.com/zerocho/nodejs-book/tree/master/ch8/8.6/learn-mongoose 
 

GitHub - ZeroCho/nodejs-book

Contribute to ZeroCho/nodejs-book development by creating an account on GitHub.

github.com

  • 위 예제의 경우 프로젝트 세팅 후 콘솔을 통해 경로로 이동한 후 package.json 설치
# 콘솔
npm i express morgan nunjucks mongoose

npm i -D nodemon
  • /schemas/index.js
const mongoose = require('mongoose');

const connect = () => {
  if (process.env.NODE_ENV !== 'production') {
    mongoose.set('debug', true);    // 개발 시 debug 모드 true.(쿼리가 콘솔에 보이도록 해줌)
  }

  // mongodb://설정한 관리자아이디:비밀번호@localhost:27017/admin
  mongoose.connect('mongodb://root:nodejsbook@localhost:27017/admin', { // 로그인을 위한 db
    dbName: 'nodejs',   // 실제로 데이터 저장할 db명
    useNewUrlParser: true,
    useCreateIndex: true,
  }, (error) => {
    if (error) {
      console.log('몽고디비 연결 에러', error);
    } else {
      console.log('몽고디비 연결 성공');
    }
  });
};

// 에러 기록하는 이벤트 리스너.
mongoose.connection.on('error', (error) => {
  console.error('몽고디비 연결 에러', error);
});

// 연결 끊겼을 때 연결 재시도 코드.
mongoose.connection.on('disconnected', () => {
  console.error('몽고디비 연결이 끊겼습니다. 연결을 재시도합니다.');
  connect();
});

module.exports = connect;
  • app.js
const express = require('express');
const path = require('path');
const morgan = require('morgan');
const nunjucks = require('nunjucks');

const connect = require('./schemas');
const indexRouter = require('./routes/index');
const usersRouter = require('./routes/users');
const commentsRouter = require('./routes/comments');

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

app.use(morgan('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));

app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/comments', commentsRouter);

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'), '번 포트에서 대기 중');
});

 

10. 몽구스 스키마 사용 (학습중)

### 스키마 정의하기

  • schemas 폴더 안에 작성. MySQL의 테이블처럼 정해진 데이터만 들어갈 수 있게 강제함
  • type은 자료형, require는 필수 여부 default는 기본값, unique는 고유 여부.
  • schemas/user.js
const mongoose = require('mongoose');

const { Schema } = mongoose;
const userSchema = new Schema({
    name : {
        type: String,
        required: true, // required: 필수여부.
        unique: true,
    },
    age : {
        type: Number,
        required: true,
    },
    married: {
        type: Boolean,
        required: true,
    },
    comment: String,    // 이 경우 required는 false. (필수값 x)
    createdAt: {
        type: Date,
        default: Date.now,  // 현재시간
    },
});

module.exports = mongoose.model('User', userSchema);
  • schemas/comment.js
const mongoose = require('mongoose');

const { Schema } = mongoose;
const { Types: { ObjectId } } = Schema;
const commentSchema = new Schema({
    commenter: {
        type: ObjectId,
        required: true,
        ref: 'User',    // 관계 설정 : user를 적음으로써 user 스키라마의 ObjectId를 의미.
    },
    comment: {
        type: String,
        required: true,
    },
    createdAt: {
        type: Date,
        default: Date.now,
    },
});

module.exports = mongoose.model('Comment', commentSchema);

### 라우터 작성하기

  • 프론트엔드 코드는 서버에 요청을 보내는 AJAX 요청 위주로, 서버 코드는 응답을 보내는 라우터 위주로 살펴보기.

### 사용자 라우터

  • router.get, post, put, patch, delete 라우터 작성.
  • routes/uses.js
const express = require('express');
const User = require('../schemas/user');
const Comment = require('../schemas/comment');

const router = express.Router();

router.route('/')
    .get(async (req, res, next) => {
        try {
            const users = await User.find({});
            res.json(users);
        } catch (err) {
            console.error(err);
            next(err);
        }
    })
    .post(async (req, res, next) => {
        try {
            const user = await User.create({
                name: req.body.name,
                age: req.body.age,
                married: req.body.married,
            });
            console.log(users);
            res.status(201).json(user);
        } catch (err) {
            console.error(err);
            next(err);
        }
    });

router.get('/:id/comments', async (req, res, next) => {
    try {
        const comments = await Comment.find({ commenter: req.params.id })
            .populate('commenter');
        console.log(comments);
        res.json(comments);
    } catch (err) {
        console.error(err);
        next(err);
    }
});

module.exports = router;
  • 몽구스의 populate는 자바스크립트로 하는 거라서 속도가 느리다.

### 댓글 라우터

  • router.get, post, put, patch, delete 라우터 작성.
  • routes/comments.js
const express = require('express');
const Comment = require('../schemas/comment');

const router = express.Router();

router.post('/', async (req, res, next) => {
    try {
        const comment = await Comment.create({
            commenter: req.body.id,
            comment: req.body.comment,
        });
        console.log(comment);
        const result = await Comment.populate(comment, { path: 'commenter' });
        res.status(201).json(result);
    } catch (err) {
        console.error(err);
        next(err):
    }
});

router.route('/:id')
    .patch(async (req, res, next) => {
        try {
            const result = await Comment.update({
                _id: req.params.id,
            }, {
                comment: req.body.comment,
            });
            res.json(result);
        } catch (err) {
            console.error(err);
            next(err);
        }
    })
    .delete(async (req, res, next) => {
        try {
            const result = await Comment.remove({ _id: req.params.id });
            res.json(result);
        } catch (err) {
            console.error(err);
            next(err);
        }
    });

module.exports = router;

### 라우터 연결하기

  • app.js에 연결
  • app.js
const connect = require('./schemas');
const indexRouter = require('./routes');
const usersRouter = require('./routes/users');
const commentsRouter = require('./routes/comments');

const app = express();
...
app.use(express.urlencoded({ extended: false });

app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/comments', commentsRouter);

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

반응형
반응형

# 익스프레스 웹 서버 만들기

1. express 서버 사용해보기

  • http 모듈로 웹 서버를 만들 때 코드가 보기 좋지 않고, 확장성도 떨어짐( 프레임워크로 해결. )
  • 대표적인 것이 Express(익스프레스) / Koa(코아) / Hapi(하피)
  • 코드 관리도 용이하고, 편의성이 많이 높아졌다.

## package.json 생성

  • 직접 만들거나(직접 입력 후 npm i 해줘야 package-lock.json, node_modules 생성됨), npm init 명령어 사용하여 생성.
  • nodemon : 소스 코드 변경 시 알아서 서버를 재시작해준다.
$ npm i express
$ npm i -D nodemon
const express = require('express');

const app = express();

app.set('port', process.env.PORT || 3000);

app.post('/', (req, res) => {
    res.send('hello express');
});

app.get('/about', (req, res) => {
    res.send('hello express');
});

app.listen(app.get('port'), () => {
    console.log('익스프레스 서버 실행');
});
  • package.json에서 scripts에 "start" : "nodemon app"이 등록되어있다면, npm start 입력하여 실행 가능. (그럼 자동으로 nodemon app 실행 됨)

 

2. express로 html 서빙하기

  • sendFile 이용 html 서빙가능.
const express = require('express');
const path = require('path');
const app = express();

app.set('port', process.env.PORT || 3000);

app.get('/', (req, res) => {
    res.sendFile(path.join(__dirname, 'index.html'));
});

app.post('/', (req, res) => {
    res.send('hello express');
});

app.get('/about', (req, res) => {
    res.send('hello express');
});

app.listen(app.get('port'), () => {
    console.log('익스프레스 서버 실행');
});

 

3. 미들웨어 사용

  • 메서드와 주소가 있는 형태를 라우터라고 부름.
  • 익스프레스는 기본적으로 위 > 아래로 실행하지만, 미들웨어는 next를 해줘야 다음걸로 넘어간다.
const express = require('express');
const path = require('path');
const app = express();

app.set('port', process.env.PORT || 3000);

app.use((req, res, next) => {
    console.log('모든 요청에 실행하고 싶어.');
    next();
});

app.get('/', (req, res) => {
    res.sendFile(path.join(__dirname, 'index.html'));
});

app.post('/', (req, res) => {
    res.send('hello express');
});

app.get('/about', (req, res) => {
    res.send('hello express');
});

app.listen(app.get('port'), () => {
    console.log('익스프레스 서버 실행');
});
const express = require('express');
const path = require('path');
const app = express();

app.set('port', process.env.PORT || 3000);

app.use((req, res, next) => {
    console.log('모든 요청에 실행하고 싶어.');
    next();
});

app.get('/', (req, res) => {
    res.sendFile(path.join(__dirname, 'index.html'));
});

app.post('/', (req, res) => {
    res.send('hello express');
});

app.get('/category/Javascript', (req, res) => {
    res.send('hello Javascript');
});

app.get('/category/:name', (req, res) => {
    res.send(`hello ${req.params.name}`);
});

app.get('/about', (req, res) => {
    res.send('hello express');
});

app.get('*', (req, res) => {
    res.send('와일드카드 * 는 모든 GET은 여길 실행하겠다는 것.');
});

app.listen(app.get('port'), () => {
    console.log('익스프레스 서버 실행');
});

 

4. 미들웨어 특성 이해

  • req, res, next를 매개변수로 가지는 함수
app.use((req, res, next) => {
	console.log('모든 요청에 다 실행됩니다.');
	next();
});
  • 익스프레스 미들웨어들도 다음과 같이 축약 가능. 
순서가 중요함

static 미들웨어에서 파일을 찾으면 next를 호출 안 하므로 json, urlencoded, cookieParser는 실행되지 않음.

app.use(
	morgan('dev'),
	express.static('/', path.join(__dirname, 'public')),
	express.json(),
	express.urlencoded({ extended : false }),
	cookieParser(process.env.COOKIE_SECRET),
);
  • 익스프레스는 미들웨어로 구성됨
요청과 응답의 중간에 위치하여 미들웨어

app.use(미들웨어)로 장착

위에서 아래로 순서대로 실행됨.

미들웨어는 req, res, next가 매개변수인 함수.

req : 요청 / res : 응답 조작 가능

next()로 다음 미들웨어로 넘어감
  • 함수, 미들웨어
app.get('/about', (req, res) => {
    res.send('hello express');
});

에서..

1. 함수는
app.get(); 부분


2. 미들웨어는
함수 안에있는 
(req, res) => {
    res.send('hello express');
} 부분
  • 아래와 같이 미들웨어를 여러개 동시에 넣어줄수 있음. (연달아 사용 가능)
app.use((req, res, next) => {
    console.log('모든 요청에 실행하고 싶어.');
    next();
}, (req, res, next) => {
    console.log('모든 요청에 실행하고 싶어.');
    next();
}, (req, res, next) => {
    console.log('모든 요청에 실행하고 싶어.');
    next();
});
  • 에러 처리 미들웨어 : 에러가 발생하면 에러 처리 미들웨어로.
err, req, res, next까지 매개변수가 4개

첫 번째 err에는 에러에 관한 정보가 담김

res.status 메서드로 HTTP 상태 코드를 지정 가능하다. (기본값은 200)

에러 처리 미들웨어를 연결하지 않아도 익스프레스가 에러를 알아서 처리해주긴 함.

특별한 경우가 아니면 아래에 위치하도록 한다.
  • 에러 처리는 매우 중요. 에러 처리 로직의 경우 가장 마지막에 작성
// 에러 미들웨어의 경우 err, req, res, next 모두 들어있어야 함(반드시!!).
app.use((err, req, res, next) => {
    console.error(err);
})



// 아래와 같이.. a와 b는 다름
1. a
app.use((err, req, res, next) => {
    console.error(err);
})

2. b
app.use((err, req, res) => {
    console.error(err);
})

a 와 b는 다른함수로 취급 됨. next 생략 하지 말 것.
  • 라우터 뒤에 아래와 같이 처리 가능 (401, 403, 404 등 관련 코드를 다르게 해줄 수도 있음)
app.get((req, res, next) => {
    res.status(200).send('200이지렁!!');
});

app.get((req, res, next) => {
    res.status(404).send('404이지렁!!');
});

// 이렇게 401이지만 다른 문구 등이 표시되도록 할 수 있음. (해커들에게 정보 노출을 숨길수 있음)
app.get((req, res, next) => {
    res.status(401).send('404이지렁!!');
});
  • 하나의 라우터에서 아래와 같이 여러번 하면 에러 발생 가능.
app.get('/', (req, res, next) => {
    res.sendFile(path.join(__dirname, 'index.html'));
    res.send('hi');
    res.json({ hello: 'zeroro' });
});

=> 에러문구
Cannot set headers after they are sent to the client
  • 미들웨어의 특성 : req, res, next를 매개변수로 가지는 함수.
app.use((req, res, next) => {
	console.log('모든 요청에 다 실행 됨');
    next();
});
  • 익스프레스 미들웨어들도 다음과 같이 축약 가능하다.
순서가 중요함.

static 미들웨어에서 파일을 찾으면 next를 호출 안 하므로 json, urlencoded, cookieParser는 실행되지 않는다.

app.use(
	morgan('dev'),
    express.static('/', path.join(__dirname, 'public')),
    express.json(),
    express.urlencoded({ extended: false }),
    cookieParser(process.env.COOKIE_SECRET),
);
  • 자주 쓰는 미들웨어
morgan, cookie-parser, express-session 설치.

app.use로 장착한다.

내부에서 알아서 next를 호출해서 다음 미들웨어로 넘어간다.

 

5. next 활용법

  • next를 호출해야 다음 코드로 넘어간다.
next를 주석 처리하면 응답이 전송되지 않는다.

다음 미들웨어(라우터 미들웨어)로 넘어가지 않기 때문.

next에 인수로 값을 넣으면 에러 핸들러로 넘어간다. ('route'인 경우 라우터로 넘어간다.)
  • res.json은 응답을 보낼 뿐이지 함수 자체를 종료하는게 아님. 그래서 아래와 같이 작성 시 console.log 실행됨.
app.get('/', (req, res, next) => {
    res.json({ hello: 'zeroro' });
    console.log('실행 안되겠지?');
});
  • 보통 에러 처리는 next 이용. next에 에러를 넣어서 에러 처리 미들웨어로 넘긴다.
app.use((req, res, next) => {
    console.log('1 요청을 실행하고 싶어요.');
    next();
}, (req, res, next) => {
    try {
        console.log(asdsada);
    } catch (error) {
        next(error);
    }
});


app.use((err, req, res, next) => {
    console.error(err);
    res.status(200).send('에러 났지렁!');
});
  • next('route')의 경우 다음 라우터로 넘어감.
app.get('/', (req, res, next) => {
    res.sendFile(path.join(__dirname, 'index.html'));
    next('route');  // 다음 라우터로 넘어감.
}, (req, res) => {
    console.log('실행되나?');   // 실행 x
});

app.get('/', (res, req, next) => {
    console.log('실행됨');  // 실행됨.
});



// 아래와 같이 if문에 따라 어떤 미들웨어 실행할지 가능. (next 분기처리)
app.get('/', (req, res, next) => {
    res.sendFile(path.join(__dirname, 'index.html'));
    if (true) {
        next('route');  // if문이 true면 다음 라우터(실행됨)로 넘어감.
    } else {
        next();	// if문이 false면 (실행되나?)로 넘어감.
    }
}, (req, res) => {
    console.log('실행되나?');
});

app.get('/', (res, req, next) => {
    console.log('실행됨');
});

### 미들웨어 간 데이터 전달하기

  • req나 res 객체 안에 값을 넣어 데이터 전달 가능
  • app.set과의 차이점 : app.set은 서버 내내 유지, req, res는 요청 하나 동안만 유지
  • req.body나 req.cookies 같은 미들웨어의 데이터와 겹치지 않게 조심해야 함.
app.use((req, res, next) => {
	req.data = '데이터 넣기';
    next();
}, (req, res, next) => {
	console.log(req.data);	// 데이터 받기
    next();
});
# 미들웨어 간 데이터 전달 예_1
app.use((req, res, next) => {
	req.session.data = 'zerocho 비번';
});

app.get('/', (req, res, next) => {
	req.session.data	// 'zerocho 비번'
    res.sendFile(path.join(__dirname, 'index.html'));
});

=> 계속 유지되고 싶은 데이터일때 사용.
단점 : 다음 요청때도 데이터가 남아있게 됨.


# 미들웨어 간 데이터 전달 예_2
app.use((req, res, next) => {
	req.data = 'zerocho 비번';
});

app.get('/', (req, res, next) => {
	req.data	// 'zerocho 비번'
    res.sendFile(path.join(__dirname, 'index.html'));
});

=> 요청 한번만 할 경우 사용.

### 미들웨어 확장하기

  • 미들웨어 안에 미들웨어를 넣는 방법
방법1)
app.use(morgan('dev'));


방법2)
app.use((req, res, next) => {
	morgan('dev')(req, res, next);
});
  • 아래처럼 다양하게 활용 가능
app.use((req, res, next) => {
	if (process.env.NODE_ENV === 'production') {
    	morgan('combined')(req, res, next);
    } else {
    	morgan('dev')(req, res, next);
    }
});
app.use('/', express.static(path.join(__dirname, 'public'))); 를 예로..


# 미들웨어 확장법 이용, 로그인 한 대상에게만 express.static 실행되도록 하려면
app.use('/', (req, res, next) => {
	if (req.session.id) {	// 로그인 시.
    	express.static(path.join(__dirname, 'public')))(req, res, next);
    } else {
		next();
	}
});

 

6. morgan, bodyParser, cookieParser

### morgan

  • 서버로 들어온 요청과 응답을 기록해주는 미들웨어이다.
  • 로그의 자세한 정도 선택 가능(dev, tiny, short, common, combined)
app.use(morgan('dev'));
  • morgan 예 (dev, combined)
const express = require('express');
const path = require('path');
const morgan = require('morgan');
const cookieParser = require('cookie-parser');

const app = express();

app.set('port', process.env.PORT || 3000);

// 개발 시 사용.
app.use(morgan('dev'));

// 실무에서 사용(배포 시?)
// app.use(morgan('combined'));

app.use(cookieParser());

### cookie-parser

  • 요청 헤더의 쿠키를 해석해주는 미들웨어
  • parseCookies 함수와 기능이 비슷.
  • req.cookies 안에 쿠키들이 들어있다.
# app.js

app.use(cookieParser(비밀키));

비밀키로 쿠키 뒤에 서명을 붙여 내 서버가 만든 쿠키임을 검증할 수 있다.
  • 실제 쿠키 옵션들을 넣을 수 있다. (expires, domain, httpOnly, maxAge, path, secure, sameSite 등, 지울 때는 clearCookie로 (expires와 maxAge를 제외한 옵션들이 일치해야 함))
res.cookie('name', 'zerocho', {
	expires : new Date(Date.now() + 900000),
    httpOnly : true,
    secure : true,
});

res.ClearCookie('name', 'zerocho', { httpOnly : true, secure : true });
  • cookieParser 예
const express = require('express');
const path = require('path');
const morgan = require('morgan');
const cookieParser = require('cookie-parser');

const app = express();

app.set('port', process.env.PORT || 3000);

// 개발 시 사용.
app.use(morgan('dev'));

// 실무에서 사용
// app.use(morgan('combined'));

app.use(cookieParser());

// cookieParser 이용 예.
app.get('/', (req, res) => {
    req.cookies // { mycookie: 'test' 등으로 }알아서 파싱됨 

    //'Set-Cookie' : `name=${encodeURIComponent(name)}; Expires=${expires.toGMYString()}; HttpOnly; Path=/`,
    res.cookie('name', encodeURIComponent(name), {
        expires: new Date(),
        httpOnly: true,
        path: '/',
    })
    // 쿠키 삭제 시.
    res.clearCookie('name', encodeURIComponent(name), {
        httpOnly: true,
        path: '/',
    })
    res.sendFile(path.join(__dirname, 'index.html'));
});
  • cookieParser 엄호화? 서명된 쿠키 사용 예 (req.signedCookies 이용)
const express = require('express');
const path = require('path');
const morgan = require('morgan');
const cookieParser = require('cookie-parser');

const app = express();

app.set('port', process.env.PORT || 3000);

// 개발 시 사용.
app.use(morgan('dev'));

// 실무에서 사용
// app.use(morgan('combined'));

app.use(cookieParser(abcdefg));

app.get('/', (req, res) => {
    req.cookies // { mycookie: 'test' 등으로 }알아서 파싱됨 
    req.signedCookies   // 서명된 쿠키.
    //'Set-Cookie' : `name=${encodeURIComponent(name)}; Expires=${expires.toGMYString()}; HttpOnly; Path=/`,
    res.cookie('name', encodeURIComponent(name), {
        expires: new Date(),
        httpOnly: true,
        path: '/',
    })
    // 쿠키 삭제 시.
    res.clearCookie('name', encodeURIComponent(name), {
        httpOnly: true,
        path: '/',
    })
    res.sendFile(path.join(__dirname, 'index.html'));
});

### body-parser

  • 요청의 본문을 해석해주는 미들웨어
  • 폼 데이터나 AJAX 요청의 데이터 처리
  • json 미들웨어는 요청 본문이 json인 경우 해석, urlencoded 미들웨어는 폼 요청 해석
  • put이나 patch, post 요청 시에 req.body에 프런트에서 온 데이터를 넣어준다.
app.use(express.json());
app.use(express.urlencoded({ extended: false });
  • 버퍼 데이터나 text 데이터일 때는 body-parser를 직접 설치해야 함.
콘솔
$ npm i body-parser
const bodyParser = require('body-parser');
app.use(bodyParser.raw());
app.use(bodyParser.text());
  • bodyParser 예
app.use(express.json());
app.use(express.urlencoded({ extended : true }));	
// 클라이언트에서 form submit 할때_form 파싱 (extended는 쿼리스트링 처리관련. true일 경우 qs / false면 쿼리스트링 모듈 사용)

위 2개를 넣어주면 아래와 같이 사용 가능.
req.body.name
  • Multipart 데이터(이미지, 동영상 등)인 경우는 다른 미들웨어를 사용해야 함.

 

7. static 미들웨어

  • 정적인 파일들을 제공하는 미들웨어
  • 인수로 정적 파일의 경로를 제공
  • 파일이 있을 때 fs.readFile로 직접 읽을 필요 없음.
  • 요청하는 파일이 없으면 알아서 next를 호출해 다음 미들웨어로 넘어간다.
  • 파일을 발견했다면 다음 미들웨어는 실행되지 않는다.
// app.use('요청 경로', express.static('실제 경로'));
app.use('/', express.static(path.join(__dirname, 'public')));
  • 컨텐츠 요청 주소와 실제 컨텐츠의 경로를 다르게 만들 수 있음. 이로 인해 서버의 구조를 파악하기 어려워져서 보안에 도움이 된다.
예_1)
요청경로 : localhost:3000/zerocho.html
실제경로 : learn-express/public-3030/zerocho.html

예_2)
요청경로 : localhost:3000/hello.css
실제경로 : learn-express/public-3030/hello.css

=> 
예1, 예2 처럼 사용하면 서버 구조를 예측할 수 없어서 좋음.
  • static의 경우 해당하는 파일을 찾은 경우 next를 진행하지 않음. 그래서 미들웨어 간의 순서가 중요함. (위에서 실행될 경우 밑에 미들웨어들이 실행되지 않을 수 있음)

 

8. express-session 미들웨어

  • 세션 관리용 미들웨어
app.use(session({
    resave: false,
    saveUninitialized: false,
    secret:'zeropassword',
    cookie: {
        httpOnly: true, // 자바스크립트로 공격당하지 않기 위해 항상 설정.
        secure: false,        
    },
    name: 'connect.sid',    // name의 기본값 : connect.sid(변경가능, 서명되어있어서 읽을수 없는 문자열로 바뀜)
}));


세션 쿠키에 대한 설정(secret: 쿠키 암호화, cookie: 세션 쿠키 옵션)

세션 쿠키는 앞에 s%3A가 붙은 후 암호화되어 프런트에 전송됨.

resave : 요청이 왔을 때 세션에 수정사항이 생기지 않아도 다시 저장할지 여부

saveUninitialized : 세션에 저장할 내역이 없더라도 세션을 저장할지

req.session.save로 수동 저장도 가능하지만 할 일 거의 없음.
  • express-session은 요청마다 개인의 저장 공간을 만들어주는 것과 같음.
const session = require('express-session');
const app = express();

app.use(session());

app.get('/', (req, res, next) => {
    req.session.id = 'hello';   // 이렇게 할 경우 요청을 보낸 사람의 id만 hello가 됨
    res.sendFile(path.join(__dirname, 'index.html'));
})

 

9. 멀티파트 데이터 형식 (multer 사용하기)

  • form 태그의 enctype이 multipart/form-data인 경우
  • body-parser로는 요청 본문을 해석할 수 없음
  • multer 패키지 필요.
# 콘솔
$ npm i multer
  • multipart.html
<form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="image" />
    <input type="text" name="title" />
    <button type="submit">업로드</button>
</form>

### multer 미들웨어들

  • single과 none, array, fields 미들웨어 존재.
  • single은 하나의 파일을 업로드 할 때, none은 파일은 업로드하지 않을 때
  • req.file 안에 업로드 정보 저장
# upload.single
app.post('/upload', upload.single('image'), (req, res) => {
	console.log(req.file, req.body);
    res.send('ok');
});

# upload.none
app.post('/upload', upload.none(), (req, res) => {
	console.log(req.body);
    res.send('ok');
});
  • array와 fields는 여러 개의 파일을 업로드 할 때 사용.
  • array는 하나의 요청 body 이름 아래 여러 파일이 있는 경우.
  • fields는 여러 개의 요청 body 이름 아래 파일이 하나씩 있는 경우.
  • 두 경우 모두 업로드된 이미지 정보가 req.files 아래에 존재.
app.post('/upload', upload.array('many'), (req, res) => {
	console.log(req.files, req.body);
    res.send('ok');
});

 

10. dotenv 사용

  • 아래와 같이 입력하여 dotenv 설치
# 콘솔
$ npm i dotenv
  • 별도의 .env 파일을 생성하여 db 패스워드 등 비밀키 저장.
# .env 파일 예시
COOKIE_SECRET=zerochocookiesecret
DB_PASSWORD=nodejsbook
  • .env 파일의 경우 드라이브 or 클라우드에 절대 올리면 안됨. 그리고 권한별로 알 수 있는 비밀키를 다르게 해줘야 함. (보안 위험)

 

11. Router 객체로 라우터 분리하기.

### express.Router

  • app.js가 길어지는 것을 막을 수 있다.
  • userRouter의 get은 /user 와 / 가 합쳐져서 GET /user/ 가 됨.
# routes/index.js
const express = require('express');

const router = express.Router();

// GET / 라우터
router.get('/', (req, res) => {
	res.send('Hello Express');
});

module.exports = router;


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

const router = express.Router();

// GET /user 라우터
router.get('/', (req, res) => {
	res.send('Hello user');
});

module.exports = router;


# app.js
...
const path = require('path');

dotenv.config();
const indexRouter = require('./routes');
const userRouter = require('./routes/user');

...

app.use('/', indexRouter);
app.use('/user', userRouter);

app.use((req, res, next) => {
	res.status(404).send('Not Found');
});

...

### 라우트 매개변수

  • :id를 넣으면 req.params.id로 받을 수 있음
  • 동적으로 변하는 부분을 라우트 매개변수로 만듦
req.get('/user/:id', function(req, res) {
	console.log(req.params, req.query);
});
  • 일반 라우터보다 뒤에 위치해야 함
req.get('/user/:id', function(req, res) {
	console.log('얘만 실행됨');
});

req.get('/user/like', function(req, res) {
	console.log('전혀 실행되지 않음');
});
  • /users/123?limit=5&skip=10 주소 요청일 경우 아래와 같이 콘솔 출력
# 콘솔
{ id: '123' } { limit: '5', skip: '10' }

### 404 미들웨어

  • 요청과 일치하는 라우터가 없는 경우를 대비해 404 라우터 만들기.
req.get((req, res, next) => {
	res.status(404).send('Not Found');
});
  • 이게 없으면 단순히 Cannot GET 주소 라는 문자열이 표시됨.

### 라우터 그룹화

  • 주소는 같지만 메서드가 다른 코드가 있을 때
req.get('/abc', (req, res) => {
	res.send('GET /abc');
});

req.post('/abc', (req, res) => {
	res.send('POST /abc');
});
  • router.route로 묶음
router.route('/abc')
	.get((req, res) => {
    	res.send('GET /abc');
	})
	.post((req, res) => {
    	res.send('POST /abc');
    });

### req

  • res.app : req 객체를 통해 app 객체에 접근할 수 있다. req.app.get('port')와 같은 식으로 사용 가능.
  • req.body : body-parser 미들웨어가 만드는 요청의 본문을 해석한 객체.
  • req.cookies : cookie-parser 미들웨어가 만드는 요청의 쿠키를 해석한 객체.
  • req.ip : 요청의 ip 주소가 담겨 있다.
  • req.params : 라우트 매개변수에 대한 정보가 담긴 객체.
  • req.query : 쿼리스트링에 대한 정보가 담긴 객체.
  • req.signedCookies : 서명된 쿠키들은 req.cookies 대신 여기에 담긴다.
  • req.get(헤더 이름) : 헤더의 값을 가져오고 싶을 때 사용하는 메서드.

### res

  • res.app : req.app 처럼 res 객체를 통해 app 객체에 접근할 수 있다.
  • res.cookie(키, 값, 옵션) : 쿠키를 설정하는 메서드.
  • res.clearCookie(키, 값, 옵션) : 쿠키를 제거하는 메서드.
  • res.end() : 데이터 없이 응답을 보냄.
  • res.json(JSON) : JSON 형식의 응답을 보냄
  • res.redirect(주소) : 리다이렉트할 주소와 함께 응답을 보냄
  • res.render(뷰, 데이터) : 템플릿 엔진을 렌더링해서 응답할 때 사용하는 메서드.
  • res.send(데이터) : 데이터와 함께 응답을 보냄. 데이터는 문자열 or HTML or 버퍼 or 배열일 수 있음
  • res.sendFile(경로) : 경로에 위치한 파일을 응답함
  • res.set(헤더, 값) : 응답의 헤더를 설정함
  • res.status(코드) : 응답 시의 HTTP 상태 코드를 지정함

### 기타

  • 메서드 체이닝 지원
res
	.status(201)
    .cookie('test', 'test')
    .redirect('/admin')
  • 응답은 한 번만 보내야 한다.
응답을 여러 번 보내는 경우 아래와 같은 에러 발생 가능.
Can't set headers after they are sent.

 

12. 템플릿 엔진

  • 템플릿 엔진은 HTML의 정적인 단점을 개선.
  • 반복문, 조건문, 변수 등을 사용할 수 있음.
  • 동적인 페이지 작성 가능
  • PHP, JSP와 유사함.

### Pug (구 Jade)

  • 문법이 Ruby와 비슷해 코드 양이 많이 줄어듬
  • HTML과 많이 달라 호불호가 갈림.
  • 익스프레스에 app.set으로 퍼그 연결
...
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

app.use(morgan('dev'));
...

### Pug_변수

  • res.render에서 두 번째 인수 객체에 Pug 변수를 넣음
router.get('/', function(req, res, next) {
	res.render('index', { title : 'Express' });
});
  • res.locals 객체에 넣는 것도 가능(미들웨어간 공유됨)
router.get('/', function(req, res, next) {
	res.locals.title = 'Express';
    res.render('index');
});
  • = 이나 #{ } 으로 변수 렌더링 가능 (= 뒤에는 자바스크립트 문법 사용 가능)
  • 퍼그 파일 안에서 변수 선언 가능. (- 뒤에 자바스크립트 사용)
  • 변수 값을 이스케이프 하지 않을 수 있음 (자동 이스케이프)
  • for in 또는 each in으로 반복문 사용 가능 (값과 인덱스 가져올 수 있음)
  • if else if else문, case when문 사용 가능

### Pug_include

  • 퍼그 파일에 다른 퍼그 파일을 넣을 수 있음
  • 헤더, 푸터, 내비게이션 등의 공통 부분을 따로 관리할 수 있어 편리함.
  • include로 파일 경로 지정

### Pug_extends와 block

  • 레이아웃을 정할 수 있음.
  • 공통되는 레이아웃을 따로 관리할 수 있어 좋음. include와도 같이 사용.

 

13. 넌적스 템플릿 엔진

  • Pug의 문법에 적응되지 않는다면 넌적스를 사용하면 좋다.
  • 아래와 같이 콘솔에 입력하여 넌적스 설치.
# 콘솔
$ npm i nunjucks
  • 확장자는 html 또는 njk(view engine을 njk로)
...
const path = require('path');
const nunjucks = require('nunjucks');

dotenv.config();
const indexRouter = require('./routes');
const userRouter = require('./routes/user');

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

### 넌적스_변수

  • {{변수}}
<h1>{{title}}</h1>
<p>Welcome to {{title}}</p>
<button class="{{title}}" type="submit">전송</button>
<input placeholder="{{title}} 연습"/>
  • 내부 변수 선언 가능 {%set 자바스크립트 구문}
# 변수 예_1
# 넌적스
{% set node = 'Node.js' %}
{% set js = 'Javascript' %}
<p>{{node}}와 {{js}}</p>


# html
<p>Node.js와 Javascript</P>



# 변수 예_2
# 넌적스
<p>{{'<strong>이스케이프</strong>'}}</P>
<p>{{'<strong>이스케이프 하지 않음</strong>' | safe }}</p>


# html
<p>&lt;strong%gt;이스케이스&lt;/strong%gt;</p>
<p><strong>이스케이프 하지 않음</strong></p>

### 넌적스_반복문

{% %} 안에 for in 작성 (익덱스는 loop 키워드)

# 반복문 예_1
# 넌적스
<ul>
	{% set fruits = ['사과', '배', '오렌지', '바나나', '복숭아'] %}
    {% for item in fruits %}
    <li>{{item}}</li>
    {% endfor %}
</ul>


# html
<ul>
	<li>사과</li>
    <li>배</li>
    <li>오렌지</li>
    <li>바나나</li>
    <li>복숭아</li>
</ul>



# 반복문 예_2
# 넌적스
<ul>
	{% set fruits = ['사과', '배', '오렌지', '바나나', '복숭아'] %}
    {% for item in fruits %}
    <li>{{loop.index}}번째 {{item}}</li>
    {% endfor %}
</ul>


# html
<ul>
	<li>1번째 사과</li>
    <li>2번째 배</li>
    <li>3번째 오렌지</li>
    <li>4번째 바나나</li>
    <li>5번째 복숭아</li>
</ul>

### 넌적스_조건문

  • {% if %} 안에 조건문 작성
# 조건문 예_1
# 넌적스
{% if isLoggedIn %}
<div>로그인 되었습니다.</div>
{% else %}
<div>로그인이 필요합니다.</div>
{% endif %}


# html
<!-- isLoggedIn이 true 일 때 -->
<div>로그인 되었습니다.</div>
<!-- isLoggedIn이 false 일 때 -->
<div>로그인이 필요합니다.</div>



# 조건문 예_2
# 넌적스
{% if fruit === 'apple' %}
<p>사과 입니다.</p>
{% elif fruit === 'banana' %}
<p>바나나 입니다.</p>
{% elif fruit === 'orange' %}
<p>오렌지 입니다.</p>
{% else %}
<p>사과도 바나나도 오렌지도 아닙니다.</P>
{% endif %}


# html
<!-- fruite이 apple일 때 -->
<p>사과 입니다.</p>
<!-- fruite이 banana일 때 -->
<p>바나나 입니다.</p>
<!-- fruite이 orange일 때 -->
<p>오렌지 입니다.</p>
<!-- 기본값 -->
<p>사과도 바나나도 오렌지도 아닙니다.</P>

### 넌적스_include

  • 파일이 다른 파일을 불러올 수 있음
  • include에 파일 경로 넣어줄 수 있음
# 넌적스
## header.html
<header>
	<a href="/">Home</a>
    <a href="/about">About</a>
</header>

## footer.html
<footer>
	<div>푸터 입니다.</div>
</footer>

## main.html
{% include "header.html" %}
<main>
	<h1>메인 파일</h1>
    <p>다른 파일을 include할 수 있습니다.</p>
</main>
[% include "footer.html" %}



# html
<header>
	<a href="/">Home</a>
    <a href="/about">About</a>
</header>
<main>
	<h1>메인 파일</h1>
    <p>다른 파일을 include할 수 있습니다.</p>
</main>
<footer>
	<div>푸터 입니다.</div>
</footer>

### 넌적스_레이아웃

  • 레이아웃을 정할 수 있음. 공통되는 레이아웃을 따로 관리할 수 있어서 좋다. (include와 같이 사용)
# 넌적스
## layout.html
<!DOCTYPE html>
<html>
    <head>
        <title>{{title}}</title>
        <link rel="stylesheet" href="/style.css">
        {% block style %}
        {% endblock %}
    </head>
    <body>
        <header>헤더입니다.</header>
        {% block content %}
        {% endblock %}
        <footer>푸터입니다.</footer>
        {% block script %}
        {% endblock %}
    </body>
</html>


## body.html
{% extends 'layout.html' %}

{% block content %}
<main>
    <p>내용입니다.</p>
</main>
{% endblock %}

{% block script %}
<script src="/main.js"></script>
{% endblock %}

### 에러 처리 미들웨어

  • 에러 발생 시 템플릿 엔진과 상관없이 템플릿 엔진 변수를 설정하고 error 템플릿을 렌더링함
  • res.locals.변수명으로도 템플릿 엔진 변수 생성 가능
  • process.env.NODE_ENV는 개발환경인지, 배포환경인지 구분해주는 속성.
...
app.use((req, res, next) => {
	const error = new Error(`${req.method} ${req.url} 라우터가 없습니다.`);
    error.status = 404;
    next(error);
});

app.use((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');
});
...
반응형

+ Recent posts