본문 바로가기

WINK-(Web & App)/Express.js (Node.js) 스터디

[2024 Node.js 스터디] 김민서 #5주차

반응형
익스프레스 프로젝트 시작하기
//package.json
{
  "name": "learn-express",
  "version": "0.0.1",
  "description": "익스프레스를 배우자",
  "main": "app.js",
  "scripts": {
    "start": "nodemon app"
  },
  "author": "ZeroCho",
  "license": "MIT"
}

항상 package.json을 제일 먼저 생성해야 한다. npm init 명령어를 사용하기 or 직접 파일 만들기

$ npm i express
$ npm i -D nodemon

scripts 부분에 start 속성은 꼭 넣어야 한다. nodemon app을 하면 app.js를 nodemon으로 실행한다는 뜻이다. 서버 코드를 수정하면 nodemon이 서버를 자동으로 재시작한다. 

//서버 역할을 하는 app.js
const express = require('express');

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

app.get('/', (req, res) => {
  res.send('Hello, Express');
});

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

app.set('port', 포트)로 서버가 실행될 포트를 설정한다. 이렇게 app.set(키, 값)을 사용해서 데이터를 저장할 수 있으며, 나중에 데이터를 app.get(키)로 가져올 수 있다.

app.get(주소, 라우터)는 주소에 대한 GET 요청이 올 때 어떤 동작을 할지 적는 부분이다. 매개변수 req는 요청에 관한 정보가 들어 있는 객체이고, res는 응답에 관한 정보가 들어 있는 객체이다.

GET 요청 외에도 POST, PUT, PATCH, DELETE, OPTIONS에 대한 라우터를 위한 app.post, app.put, app.patch. app.delete, app.options 메서드가 존재한다

 

1. 문자열 대신 HTML로 응답하기

// index.html
<html>
<head>
  <meta charset="UTF-8" />
  <title>익스프레스 서버</title>
</head>
<body>
  <h1>익스프레스</h1>
  <p>배워봅시다.</p>
</body>
</html>
// add.js
const express = require('express');
const path = require('path');

const app = express();
app.set('port', process.env.PORT || 3000);
app.get('/', (req, res) => {
  // res.send('Hello, Express');
  res.sendFile(path.join(__dirname, '/index.html'));
});

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

localhost:3000에 접속

 

 

자주 사용하는 미들웨어

요청과 응답의 중간에 위치하여 미들웨어라고 부른다. 요청과 응답을 조작하여 기능을 추가하기도하고, 나쁜 요청을 걸러내기도 한다.

// app.js에 미들웨어 연결
...
app.set('port', process.env.PORT || 3000);

app.use((req, res, next) => {
  console.log('모든 요청에 다 실행됩니다.');
  next();
});
app.get('/', (req, res, next) => {
  console.log('GET / 요청에서만 실행됩니다.');
  next();
}, (req, res) => {
  throw new Error('에러는 에러 처리 미들웨어로 갑니다.')
});

app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).send(err.message);
});

app.listen(app.get('port'), () => {
...

미들웨어는 위에서부터 아래로 순서대로 실행된다. next는 다음 미들웨어로 넘어가는 함수이며 이를 실행하지 않으면 다음 미들웨어가 실행되지 않는다. 

에러 처리 미들웨어의 매개변수는 err, req, res, next이다. 

 

dotenv패키지는 .env 파일을 읽어서 process.env로 만든다. process.env를 별도의 파일로 관리하는 이유는 보안과 설정의 편의성 때문이다. 

 

1. static

정적인 파일들을 제공하는 라우터 역할을 한다. 따로 설치할 필요 없이 express 객체 안에서 꺼내 장착하면 된다.

app.use('요청 경로' , express.static('실제 경로'));
app.use('/', express.static(path.join(__dirname, 'public')));

 

2. body-parser

요청의 본문에 있는 데이터를 해석해 req.body 객체로 만들어주는 미들웨어이다. 보통 폼 데이터나 AJAX 요청의 데이터를 처리하지만 머티파트(이미지, 동영상, 파일) 데이터는 처리하지 못한다. 

app.use(express.json());
app.use(express.urlencoded({ extended: false}));
// 버퍼나 텍스트 요청을 처리할 필요가 있을 때 콘솔에서 body-parser 설치 후
const bodyParser = require('body-parser');
app.use(bodyParser.raw());
app.use(bodyParser.text());

 

3. cookie-parser

요청에 동봉된 쿠키를 해석해 req.cookies 객체로 만든다. 

//app.js
app.use(cookieParser(비밀키));
res.cookie('name', 'zerocho', { 
  expires: new Date(Date.now() + 900000),
  httpOnly: true, 
  secure: true,
});
res.clearCookie('name', 'zerocho', { httpOnly: true, secure: true });

 

4. express-session

세션 관리용 미들웨어이다.

app.use(session({
  resave: false,
  saveUninitialized: false,
  secret: process.env.COOKIE_SECRET,
  cookie: {
    httpOnly: true,
    secure: false,
  },
  name: 'session-cookie',
}));

express-session은 인수로 세션에 대한 설정을 받으며, 세션 관리 시 클라이언트에 쿠키를 보낸다.

 

5. multer

이미지, 동영상 등을 비롯한 여러 가지 파일들을 멀티파트 형식으로 업로드할 때 사용하는 미들웨어이다.

//app.js
const multer = require('multer');
const fs = require('fs');

try {
  fs.readdirSync('uploads');
} catch (error) {
  console.error('uploads 폴더가 없어 uploads 폴더를 생성합니다.');
  fs.mkdirSync('uploads');
}
const upload = multer({
  storage: multer.diskStorage({
    destination(req, file, done) {
      done(null, 'uploads/');
    },
    filename(req, file, done) {
      const ext = path.extname(file.originalname);
      done(null, path.basename(file.originalname, ext) + Date.now() + ext);
    },
  }),
  limits: { fileSize: 5 * 1024 * 1024 },
});
app.get('/upload', (req, res) => {
  res.sendFile(path.join(__dirname, 'multipart.html'));
});

app.post('/upload', upload.single('image'), (req, res) => {
  console.log(req.file);
  res.send('ok');
});
//multipart.html
<form id="form" action="/upload" method="post" enctype="multipart/form-data">
  <input type="file" name="image1" />
  <input type="file" name="image2" />
  <input type="text" name="title" />
  <button type="submit">업로드</button>
</form>

 

 

Router 객체로 라우팅 분리하기
//routes/index.js
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');
...
  name: 'session-cookie',
}));

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

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

app.use((err, req, res, next) => {
...​

localhost:3000에서는 Hello, Express

localhost:3000/user에서는 Hello, User

 

req, res 객체
  •  req.app: req 객체를 통해 app 객체에 접근할 수 있다
  • req.body: body-parser 미들웨어가 만드는 요청의 본문을 해석한 객체
  • req.cookies: cookie-parser 미들웨어가 만드는 요청의 쿠키를 해석한 객체
  • req.ip: 요청의 ip 주소가 담겨 있다
  • req.params: 라우트 매개변수에 대한 정보가 담긴 객체
  • req.query: 쿼리스트링에 대한 정보가 담긴 객체
  • req.signedCookies: 서명된 쿠키들은 req.cookies 대신 여기에 담겨 있다
  • req.get(헤더 이름): 헤더의 값을 가져오고 싶을 때 사용하는 메서드
  • res.app: req.app처럼 res 객체를 통해 app 객체에 접근할 수 있다
  • res.cookie(키, 값, 옵션): 쿠키를 설정하는 메서드
  • res.clearCookie(키, 값, 옵션): 쿠키를 제거하는 메서드
  • res.end(): 데이터 없이 응답을 보낸다
  • res.json(JSON): JSON 형식의 응답을 보낸다
  • res.locals: 하나의 요청 안에서 미들웨어 간에 데이터를 전달하고 싶을 때 사용하는 객체
  • res.redirect(주소): 리다이렉트할 주소와 함께 응답을 보낸다
  • res.render(뷰, 데이터): 다음 절에서 다룰 템플릿 엔진을 렌더링해서 응답할 때 사용하는 메서드
  • res.send(데이터): 데이터와 함께 응답을 보낸다
  • res.sendFile(경로): 경로에 위치한 파일을 응답한다
  • res.set(헤더, 값): 응답의 헤더를 설정한다
  • res.status(코드): 응답 시의 HTTP 상태 코드를 지정한다

 

템플릿 엔진

1. 퍼그(제이드)

익스프레스와 연결하기

// app.js
...
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

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

 

- 기존 HTML과 다르게 <>가 없다

- 탭 또는 스페이스로만 태그의 부모자식 관계를 규명한다

- HTML 텍스트는 태그 또는 속성 뒤에 한 칸을 띄고 입력한다 (파이프를 넣으면 여러 줄 입력)

- style이나 script 태그로 css 또는 자바스크립트 코드를 작성할 땐 태그 뒤에 점을 붙인다

- 자바스크립트 변수를 템플릿에 렌더링 할 수 있다

- 기본적으로 변수의 특수 문자를 HTML 엔티티로 이스케이프한다 (원하지 않으면 != 사용)

- each로 반복문을 돌릴 수 있다 (for도 가능)

- if, else if, else, case 문도 사용 가능

- include로 다른 퍼그나 HTML 파일을 넣을 수 있다

- extends와 block으로 레이아웃을 정할 수 있다

 

2. 넌적스

퍼그의 HTML 문법 변화에 적응하기 힘들 때 유용하다

HTML 문법을 그대로 사용하며 자바스크립트 문법을 사용할 수 있다

//app.js 연결하기
...
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'));
...

- 변수는 {{}}로 감싼다

- 변수선언 {%set 변수 = '값' %} 사용

- HTML을 이스케이프하고 싶지 않을 때 {{ 변수 | safe }} 사용

- 특수한 문은 {% %} 안에 쓰기

- 반복문에서 인덱스를 사용할 때 loop.index라는 특수한 변수 사용

- 조건문 {% if 변수 %} {% elif %} {% else %} {% endif %}

- case문은 없다 (elif로 분기 처리)

- include로 다른 HTML 파일을 넣을 수 있다

- extends, block로 레이아웃을 정할 수 있다

 

데이터베이스

데이터베이스는 관련성을 가지며 중복이 없는 데이터들의 집합이다. 

DBMS 중에는 RDBMS라고 부르는 관계형 DBMS가 많이 사용되며 대표적으로 Oracle, MySQL, MSSQL 등이 있다.

 

mac에 mysql, 워크벤치 설치

brew로 mysql 설치하고 실행하기
brew로 워크벤치 설치하기
커넥션 접속 화면

 

 

데이터베이스 및 테이블 생성하기

CREATE SCHEMA [데이터베이스명] : 데이터베이스를 생성하는 명령어

MySQL에서 데이터베이스 = 스키마

nodejs 데이터베이스 생성

utf8mb4 : 데이터베이스에서 한글과 이모티콘을 사용하기 위함

COLLATE : 해당 CHARACTER SET을 어떤 형식으로 정렬할 것인지를 의미함

SQL 구문을 입력할 때는 마지막에 ;을 붙여야함 (안붙이면 다른 입력이 들어오기를 계속 기다린다)

 

테이블 생성하기

테이블이란 데이터가 들어갈 수 있는 틀을 의미하며, 테이블에 맞는 데이터만 들어갈 수 있다.

사용자의 정보를 저장하는 테이블을 만드는 명령어

컬럼을 정의하면 데이터를 넣을 때 컬럼 규칙에 맞는 정보들만 넣을 수 있다.

  • INT : 정수
  • VARCHAR : 가변 길이
  • CHAR : 고정 길이
  • TEXT : 긴 글을 저장할 때 사용 (수백 자 이상일 때)
  • TINYINT : -128~127 정수 저장
  • DATETIME : 날짜와 시간에 대한 정보
  • NULL / NOT NULL : 빈칸을 허용할지 여부를 묻는 옵션
  • AUTO_INCREMENT : 숫자를 저절로 올리겠다는 뜻
  • UNSIGNED : 숫자 자료형에 적용되는 옵션 (음수는 무시되고 0~4294967295까지 저장 가능, FLOAT, DOUBLE에는 적용 불가능)
  • ZEROFILL: 숫자의 자릿수가 고정되어 있을 때 사용, 비어 있는 자리에 모두 0을 넣음
  • DEFAULT now() : 해당 컬럼에 값이 없을 떄 기본값을 대신 넣음 now()는 현재 시각
  • PRIMARY KEY : 해당 컬럼이 기본 키인 경우에 설정
  • UNIQUE INDEX : 해당 값이 고유해야 하는지에 대한 옵션
  • COMMENT : 테이블에 대한 보충 설명
  • ENGINE : MyISAM, InnoDB가 제일 많이 사용된다

테이블 확인

DROP TABLE [테이블명] : 테이블 제거

사용자의 댓글을 저장하는 테이블

외래 키 : 다른 테이블의 기본키를 저장하는 컬럼 (CONSTRAINT [제약조건명] FOREIGN KEY [컬럼명] REFERENCES [참고하는 컬럼명])

CASCADE : 사용자 정보가 수정되거나 삭제되면 그것과 연결된 댓글 정보도 같이 수정하거나 삭제하기 위해 설정 -> 데이터가 불일치하는 현상 방지

테이블 확인

 

CRUD

CRUD : Create, Read, Update, Delete -> 데이터베이스에서 많이 수행하는 네가지 작업

 

1. Create (생성)

데이터를 생성해서 데이터베이스에 넣는 작업

users 테이블에 데이터 넣기

INSERT INTO [테이블명] ([컬럼1], [컬럼2], ...) VALUES ([값1], [값2], ...)

 

2. Read(조회)

데이터베이스에 있는 데이터를 조회하는 작업

모든 데이터 조회

  • SELECT * FROM [테이블명] : 모든 데이터를 조회하는 SQL문
  • 조회를 원하는 컬럼을 SELECT 다음에 : 특정 컬럼만 조회
  • WHERE 절 : 특정 조건을 가진 데이터만 조회 (AND : 조건들을 모두 만족하는 데이터, OR : 조건들 중 어느 하나라도 만족하는 데이터)
  • ORDER BY [컬럼명] [ASC|DESC] : 정렬 (DESC : 내림차순, ASC : 오름차순)
  • LIMIT [숫자] : 조회할 로우 개수 설정
  • OFFSET [건너뛸 숫자] : 로우 개수를 설정할 때 몇 개를 건너뛸지 설정

3. Update(수정)

데이터베이스에 있는 데이터를 수정하는 작업

UPDATE [테이블명] SET [컬럼명=바꿀 값] WHERE [조건]

 

4. Delete(삭제)

데이터베이스에 있는 데이터를 삭제하는 작업

DELETE FROM [테이블명] WHERE [조건]

 

 

시퀄라이즈

MySQL 작업을 쉽게 할 수 있도록 도와주는 라이브러리

ORM : 자바스크립트 객체와 데이터베이스 릴레이션을 매핑해주는 도구

자바스크립트 구문을 알아서 SQL로 바꿔준다

 

MySQL 연결하기!!!!

package.json
index.js
app.js

디렉토리 신경 안 씀 -> 오류 -> 여러번 시도 -> 3001번 포트에서 충돌 발생 -> 3002번 포트로 바꿔줌! -> 또 충돌;; -> 죽이고 다시 실행 -> 성공

config.js

책에서는 config.json 파일 -> 비밀번호가 코드에 포함되지 않도록 하기 위해 dotenv 패키지 사용 -> .env 파일 생성

.env 파일
성공!!

 

 

모델 정의하기

MySQL에서 정의한 테이블을 시퀄라이즈에서도 정의해야한다.

User와 Comment 모델을 만들고 users 테이블과 comments 테이블에 연결

 

 

관계 정의하기
  • 1:N (일대다 관계) hasMany 메서드 사용 / 다른 모델의 정보가 들어가는 테이블에는 belongsTo 사용
  • 1:1 (일대일 관계) hasOne 메서드 사용 / belongsTo와 hasOne이 반대이면 안된다
  • N:M (다대다 관계) belongsToMany 메서드

 

쿼리 알아보기

1. 로우 생성

-- 로우 생성 SQL 문
INSERT INTO nodejs.users (name, age, married, comment) VALUES ('zero', 24, 0, '자기소개1');
//로우 생성 시퀄라이즈 쿼리
const { User } = require('../models');
User.create({
  name: 'zero',
  age: 24,
  married: false,
  comment: '자기소개1',
});

 

2. 모든 데이터 조회

-- SQL문
SELECT * FROM nodejs.users;
// 시퀄라이즈 쿼리
User.findAll({});

 

3. 데이터 하나만 가져오기

-- SQL문
SELECT * FROM nodejs.users LIMIT 1;
//시퀄라이즈 쿼리
User.findOne({});

 

4. 원하는 컬럼만 가져오기

SELECT name, married FROM nodejs.users;
User.findAll({
  attributes: ['name', 'married'],
});

 

5. where 옵션이 조건들을 나열하는 옵션

SELECT name, age FROM nodejs.users WHERE married = 1 AND age > 30;
const { Op } = require('sequelize');
const { User } = require('../models');
User.findAll({
  attributes: ['name', 'age'],
  where: {
    married: true,
    age: { [Op.gt]: 30 },
  },
});

 

6. 로우 개수 설정

SELECT id, name FROM users ORDER BY age DESC LIMIT1;
User.findAll({
  attributes: ['id', 'name'],
  order: [['age', 'DESC']],
  limit: 1,
});

 

7. 로우 수정

UPDATE nodejs.users SET comment = '바꿀 내용' WHERE id = 2;
User.update({
  comment: '바꿀 내용',
}, {
  where: { id: 2 },
});

 

8. 로우 삭제

DELETE FROM nodejs.users WHERE id = 2;
User.destory({
  where: { id: 2 },
});

 

 

관계 쿼리
  • findOne이나 findAll 메서드를 호출할 때 프로미스의 결과로 모델 반환
  • include 속성으로 특정 사용자를 가져오면서 그 사람의 댓글가지 모두 가져오기 가능
  • include나 관계 쿼리 메서드에서도 where이나 attributes 옵션 사용 가능

 

SQL 쿼리하기
const [result, metadata] = await sequelize.query('SELECT * from comments');
console.log(result);
반응형