728x90

mongodb 준비하기

늘 그렇듯 이번 데이터베이스도 docker로 준비한다.

이렇게 준비를 해두고, 실행한다.

 

mongodb 연결하기

우선 다음과 같이 라이브러리들을 설치한다.

npm install morgan nunjucks mongoose

이 mongoose가 node에 굉장히 잘맞는거로 기억이 난다.

 

import mongoose from 'mongoose';

export async function connect() {
    if (process.env.NODE_ENV !== 'production') {
        mongoose.set('debug', true);
    }

    try {
        await mongoose.connect(
            'mongodb://seungkyu:1204@localhost:27017/express?authSource=admin',
        );

        console.log('MongoDB Connected');
    } catch (err) {
        console.error(err);
    }

    mongoose.connection.on('error', (err) => console.log(err));
}

그리고 위와 같이 데이터베이스 연결을 생성하는 함수를 만들고

 

import express from 'express';
import dotenv from 'dotenv';
import { connect } from './schemas/index.js';

dotenv.config();

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

async function start() {
    try {
        await connect();

        app.listen(app.get('port'), () => {
            console.log(`Server on port ${app.get('port')}`);
        });
    } catch (error) {
        console.error('MongoDB 연결 실패:', error);
        process.exit(1);
    }
}

start();

이렇게 app.ts를 수정해준다.

 

그리고 실행해보면 아래와 같이 연결이 잘 생성되는 것을 볼 수 있다.

 

Schema 정의하기

이제 데이터를 저장하기 위한 판인 Schema를 정의해보자.

모든 테이블에 id를 습관적으로 만들었는데, mongodb는 알아서 _id로 만들어주기에 생성할 필요가 없다고 한다.

schema는 다음과 같이 작성한다.

import mongoose, { Schema } from 'mongoose';

export interface IUser {
    name: string;
    age: number;
    email: string;
    password: string;
    createdAt: Date;
    updatedAt: Date;
}

const userSchema = new Schema<IUser>(
    {
        name: {
            type: String,
            required: true,
            trim: true,
        },
        age: {
            type: Number,
            required: true,
            min: 0,
        },
        email: {
            type: String,
            required: true,
            unique: true,
            lowercase: true,
            trim: true,
        },
        password: {
            type: String,
            required: true,
        },
    },
    {
        timestamps: true, // createdAt, updatedAt 자동 생성
    },
);

export default mongoose.model<IUser>('User', userSchema);

 

import mongoose, { Schema } from 'mongoose';

export interface ILecture {
    name: string;
    code: string;
    user: mongoose.Types.ObjectId;
}

const lectureSchema = new Schema<ILecture>(
    {
        name: {
            type: String,
            required: true,
        },
        code: {
            type: String,
            required: true,
            unique: true,
        },
        user: {
            type: Schema.Types.ObjectId,
            ref: 'User',
            required: true,
        },
    },
    {
        timestamps: true,
    },
);

export default mongoose.model<ILecture>('Lecture', lectureSchema);

 

쿼리 작성하기

쿼리는 작성한 스키마를 가져와서 다음과 같이 함수를 호출하는 것으로 사용한다.

import express, { Request } from 'express';
import User from '../schemas/user.js';

const router = express.Router();

interface CreateUserRequest {
    name: string;
    age: number;
    email: string;
    password: string;
}

interface UpdateUserRequest {
    id: string;
    name: string;
    age: number;
    email: string;
    password: string;
}

router.get('/', async (_req, res) => {
    const users = await User.find({});
    res.json(users);
});

router.get('/:id', async (req, res) => {
    try {
        const user = await User.findById(req.params.id);

        if (!user) {
            return res.status(404).json({
                message: 'User not found',
            });
        }

        return res.json(user);
    } catch (_err: any) {
        return res.status(400).json({
            message: 'Invalid ObjectId',
        });
    }
});

router.post(
    '/',
    async (req: Request<object, object, CreateUserRequest>, res) => {
        const user = await User.create({
            name: req.body.name,
            age: req.body.age,
            email: req.body.email,
            password: req.body.password,
        });

        return res.status(201).json(user);
    },
);

router.patch(
    '/:id',
    async (req: Request<object, object, UpdateUserRequest>, res) => {
        const id = req.body.id;

        const user = await User.updateMany(
            {
                _id: id,
            },
            {
                name: req.body.name,
                age: req.body.age,
                email: req.body.email,
                password: req.body.password,
            },
        );

        return res.status(20).json(user);
    },
);

router.delete('/:id', async (req, res) => {
    const result = await User.deleteOne({ _id: req.params.id });

    res.json(result);
});

export default router;

 

우선 POST부터 테스트를 해보면

POST http://localhost:3000/users
Content-Type: application/json

{
  "name": "김승규",
  "age": 25,
  "email": "test@test.com",
  "password": "1234"
}

 

 

이렇게 잘 생성이 된 것을 볼 수 있다.

 

이제 GET을 테스트 해보자.

전체 조회는

GET http://localhost:3000/users
Content-Type: application/json

 

id를 사용한 조회는

GET http://localhost:3000/users/6a3fc8b574989b798e660fa2
Content-Type: application/json

이렇게 잘 조회가 되며

 

PATCH, DELETE를 테스트해보면

PATCH http://localhost:3000/users/6a3fc8b574989b798e660fa2
Content-Type: application/json

{
  "name": "한승규",
  "age": 25,
  "email": "test@test.com",
  "password": "1234"
}

 

DELETE http://localhost:3000/users/6a3fc8b574989b798e660fa2
Content-Type: application/json

 

이렇게 삭제가 성공적으로 되는 것을 볼 수 있다.

'Node > Node.js' 카테고리의 다른 글

express로 pg 사용하기  (0) 2026.06.27
express 시작하기  (0) 2026.06.27
노드 패키지 매니저  (0) 2026.06.24
노드로 http 서버 만들어보기  (0) 2026.06.23
노드 사용해보기  (0) 2026.06.22
728x90

데이터베이스 설정하기

우선 데이터베이스는 postgresql를 사용할 것이며, 늘 그렇듯 docker 위에서 돌릴 것이다.

 

우선 테이블을 좀 만들어두고, 들어가도록 하자.

create table public.users
(
    id     uuid not null
        constraint users_pk
            primary key,
    name   text not null,
    age    integer,
    remark text
);

 

sequelize

일단 ORM의 한 종류라고 한다.

객체와 데이터베이스의 Relatiuon을 Mapping해준다.

우선 Sequelize, Postgresql 라이브러리들을 설치해주자.

{
  "name": "express",
  "version": "0.0.1",
  "type": "module",
  "description": "study express",
  "main": "dist/index.js",
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "build": "tsc",
    "start": "node dist/index.js"
  },
  "dependencies": {
    "@eslint/js": "^10.0.1",
    "body-parser": "^2.3.0",
    "cookie-parser": "^1.4.7",
    "dotenv": "^17.4.2",
    "eslint": "^10.5.0",
    "eslint-config-prettier": "^10.1.8",
    "eslint-plugin-prettier": "^5.5.6",
    "express": "^5.2.1",
    "express-session": "^1.19.0",
    "morgan": "^1.11.0",
    "multer": "^2.2.0",
    "nunjucks": "^3.2.4",
    "pg": "^8.22.0",
    "pg-hstore": "^2.3.4",
    "sequelize": "^6.37.8"
  },
  "devDependencies": {
    "@types/cookie-parser": "^1.4.10",
    "@types/express": "^5.0.6",
    "@types/express-session": "^1.19.0",
    "@types/morgan": "^1.9.10",
    "@types/multer": "^2.1.0",
    "@types/node": "^26.0.1",
    "@types/nunjucks": "^3.2.6",
    "@types/pg": "^8.20.0",
    "globals": "^17.7.0",
    "prettier": "^3.8.4",
    "sequelize-cli": "^6.6.5",
    "tsx": "^4.22.4",
    "typescript": "^5.5.3",
    "typescript-eslint": "^8.62.0"
  },
  "private": true
}

 

그 다음에 다시 콘솔에

 npx sequelize init

다음과 같이 입력해주면 models,mirgrations,seeders라는 폴더들이 생긴다.

 

models의 index.ts를 다음과 같이 작성해주고

import { Sequelize } from 'sequelize';
import config from '../config/config.json' with { type: 'json' };

const env = process.env.NODE_ENV ?? 'development';

const dbConfig = config[env as keyof typeof config];

export const sequelize = new Sequelize(
    dbConfig.database,
    dbConfig.username,
    dbConfig.password ?? '',
    {
        host: dbConfig.host,
        dialect: dbConfig.dialect as 'postgres',
        logging: false,
    },
);

app.ts를 다음과 같이 수정한 후 시작해보면

import express from 'express';
import dotenv from 'dotenv';
import nunjucks from 'nunjucks';
import { sequelize } from './models/index.js';

dotenv.config();

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

nunjucks.configure('views', {
    express: app,
    watch: true,
});

sequelize
    .sync({ force: false })
    .then(() => {
        console.log('connect to database');
    })
    .catch(console.error);

app.listen(app.get('port'), () => console.log('Server on'));

데이터베이스에 연결되었다고 뜨는 것을 볼 수 있다.

 

모델 정의하기

이제 저 위에 작성했던 테이블을 express의 모델로도 작성해보자.

import { DataTypes, Model, Sequelize } from 'sequelize';

export class User extends Model {
    declare id: string;
    declare name: string;
    declare age: number | null;
    declare remark: string | null;

    static initModel(sequelize: Sequelize) {
        return User.init(
            {
                id: {
                    type: DataTypes.UUID,
                    primaryKey: true,
                    allowNull: false,
                },
                name: {
                    type: DataTypes.TEXT,
                    allowNull: false,
                },
                age: {
                    type: DataTypes.INTEGER,
                    allowNull: true,
                },
                remark: {
                    type: DataTypes.TEXT,
                    allowNull: true,
                },
            },
            {
                sequelize,
                modelName: 'User',
                tableName: 'users',
                timestamps: false,
                underscored: false,
            },
        );
    }
}

여기서는 init을 사용했는데 init 메서드에서는 테이블에 대한 설정을 한다.

이제 관계를 만들어보기 위해 다음과 같은 테이블을 만들어보자.

create table public.lecture
(
    id         uuid      not null
        constraint lecture_pk
            primary key,
    user_id    uuid      not null
        constraint lecture_users_id_fk
            references public.users,
    name       text      not null,
    created_at timestamp not null
);

 

이거도 model로 정의하면

import { DataTypes, Model, Sequelize } from 'sequelize';

export class Lecture extends Model {
    declare id: string;
    declare userId: string;
    declare name: string;
    declare createdAt: Date;

    static initModel(sequelize: Sequelize) {
        return Lecture.init(
            {
                id: {
                    type: DataTypes.UUID,
                    primaryKey: true,
                    allowNull: false,
                    defaultValue: DataTypes.UUIDV4,
                },
                userId: {
                    field: 'user_id',
                    type: DataTypes.UUID,
                    allowNull: false,
                },
                name: {
                    type: DataTypes.TEXT,
                    allowNull: false,
                },
                createdAt: {
                    field: 'created_at',
                    type: DataTypes.DATE,
                    allowNull: false,
                },
            },
            {
                sequelize,
                modelName: 'Lecture',
                tableName: 'lecture',
                timestamps: false,
                underscored: true,
            },
        );
    }
}

 

이제 관계를 정의해보자, 현재 user와 lecture는 1:N의 관계이다.

 

여기서 1에 해당하는 친구가 hasMany를 통해 관계를 정의하고, N에 해당하는 친구가 belongsTo에 해당하는 관계를 정의한다.

해당 코드를 index.ts에 추가하자.

User.initModel(sequelize);
Lecture.initModel(sequelize);

User.hasMany(Lecture, {
    foreignKey: 'userId',
    as: 'lectures',
});

Lecture.belongsTo(User, {
    foreignKey: 'userId',
    as: 'user',
});

만약 1:1의 관계면 여기서 hasMany가 아닌 hasOne을 사용하며, N:M은 알아서 매핑 테이블 만들도록 하자.

 

쿼리

자 이제 모델을 모두 정의했으니 ORM을 통해 데이터베이스를 조작해보자.

 

  • insert
import { User } from "./models/user.js";

const user = await User.create({
  id: crypto.randomUUID(),
  name: "홍길동",
  age: 25,
  remark: "테스트 사용자",
});
  • select

전체조회는 그냥 다 가져오면 된다.

const users = await User.findAll();

 

가장 많이 사용하는 pk를 통한 조회는 다음과 같다.

const user = await User.findByPk("5d75d713-46bb-48fd-9a53-cbbda442ff3d");

 

조건을 많이 쓰고 싶다면 이렇게 where에 조건을 나열하면 되고

const users = await User.findAll({
  where: {
    age: 25,
    remark: "학생",
  },
});

 

하나만 조회하고 싶다면 findAll이 아닌 findOne을 사용한다.

const user = await User.findOne({
  where: {
    name: "홍길동",
  },
});
  • update

where에 조건을 작성하고, 그 위에 바꿀 값들을 작성한다.

await User.update(
  {
    age: 30,
    remark: "수정됨",
  },
  {
    where: {
      id: "사용자 UUID",
    },
  }
);

 

몇개가 업데이트 되었는지도 조회가 가능하다.

const [count] = await User.update(
  {
    age: 30,
  },
  {
    where: {
      name: "홍길동",
    },
  }
);
  • delete

삭제는 다음과 같은 코드로 작성하며

await User.destroy({
  where: {
    id,
  },
});

 

여기서도 삭제된 데이터의 개수를 알 수 있다.

const count = await User.destroy({
  where: {
    id,
  },
});

 

관계를 통해 조회하기

만약 특정 강의를 듣는 사용자를 검색하고 싶으면, join을 사용해야 한다.

강의를 조회하고, 그 안에 include로 사용자들을 넣어서 가져오면 된다.

const lectures = await Lecture.findAll({
  where: {
    name: "Express",
  },
  include: [
    {
      model: User,
      as: "user",
    },
  ],
});

'Node > Node.js' 카테고리의 다른 글

express로 mongodb 사용하기  (0) 2026.06.27
express 시작하기  (0) 2026.06.27
노드 패키지 매니저  (0) 2026.06.24
노드로 http 서버 만들어보기  (0) 2026.06.23
노드 사용해보기  (0) 2026.06.22
728x90

익스프레스로 프로젝트 만들어보기

{
  "name": "study",
  "version": "0.0.1",
  "description": "study",
  "license": "ISC",
  "author": "",
  "type": "module",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "tsc",
    "start": "ts-node src/app.ts"
  },
  "dependencies": {
    "express": "^5.2.1"
  },
  "devDependencies": {
    "@types/express": "^5.0.6",
    "@types/node": "^26.0.1",
    "nodemon": "^3.1.14",
    "ts-node": "^10.9.2",
    "typescript": "^6.0.3"
  }
}

위와 같이 package.json을 작성해주고, src/app.ts에 아래와 같이 작성하고 실행을 해보면 일단 된다.

 

import express from 'express';

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

app.get('/', (req: express.Request, res: express.Response) =>
    res.send('Hello Seungkyu'),
);

app.listen(app.get('port'), () => console.log('Server on'));

 

그러고 3000번 포트로 적솝해보면 다음과 같이 나온다.

app.get으로 get요청만 처리했지만 다른 메서드들도 당연히 사용이 가능하다.

 

여기에 이전처럼 html을 읽어서 전송하고 싶다면

path를 사용해서 파일을 읽고, res.sendFile로 파일을 전송해버린다.

import express from 'express';
import { fileURLToPath } from 'url';
import { dirname } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

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

app.get('/file', (req: express.Request, res: express.Response) =>
    res.sendFile(__dirname + '/test.html'),
);

app.get('/', (req: express.Request, res: express.Response) =>
    res.send('Hello Seungkyu'),
);

app.listen(app.get('port'), () => console.log('Server on'));

 

 

미들웨어

미들웨어는 요청과 응답 그 중간에 들어가서, express의 사실상 핵심의 역할을 한다고 한다.

저 app을 가져가서 app.use로 사용한다고 한다.

import express from 'express';

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

app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
  console.log('미들 웨어 지나갑니다~');
  next();
});

app.get(
  '/',
  (req: express.Request, res: express.Response, next: express.NextFunction) => {
  console.log('get 요청 지나갑니다~');
});

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

이렇게 next도 파라미터로 받고, 미들웨어에서 무언가의 작업을 처리 한 후 next()를 호출해서 다음 어딘가로 흘려준다.

만약 next()를 호출하지 않으면, 그냥 거기서 멈춰버린다.

 

미들웨어는 3가지 경우에 실행이 되는데

app.use(미들웨어) -> 모든 요청에서 실행

app.use('/한승규', 미들웨어) ->  /한승규로 들어오는 요청에서 미들웨어를 실행

app.get('/한승규', 미들웨어) -> /한승규로 들어오는 get 요청에서 미들웨어를 실행

 

자 그럼 많이 사용하는 미들웨어 라이브러리들을 살펴보자.

우선 다음친구들을 설치한다.

{
  "name": "study",
  "version": "0.0.1",
  "description": "study",
  "license": "ISC",
  "author": "",
  "type": "module",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "tsc",
    "start": "tsx src/app.ts"
  },
  "dependencies": {
    "cookie-parser": "^1.4.7",
    "dotenv": "^17.4.2",
    "express": "^5.2.1",
    "express-session": "^1.19.0",
    "morgan": "^1.11.0"
  },
  "devDependencies": {
    "@types/cookie-parser": "^1.4.10",
    "@types/express": "^5.0.6",
    "@types/express-session": "^1.19.0",
    "@types/morgan": "^1.9.10",
    "@types/node": "^26.0.1",
    "nodemon": "^3.1.14",
    "ts-node": "^10.9.2",
    "tsx": "^4.22.4",
    "typescript": "^6.0.3"
  }
}

 

다음과 같이 코드를 작성해보고, 여기에 들어있는 라이브러리들을 하나씩 확인해보자.

import express from 'express';
import morgan from 'morgan';
import cookieParser from 'cookie-parser';
import session from 'express-session';
import dotenv from 'dotenv';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import path from 'node:path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

dotenv.config();

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

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 || 'secret'));
app.use(
    session({
        resave: false,
        saveUninitialized: false,
        secret: process.env.SESSION_SECRET || 'secret',
        cookie: {
            httpOnly: true,
            secure: false,
        },
        name: 'session',
    }),
);

console.log(
    (
        req: express.Request,
        _res: express.Response,
        _next: express.NextFunction,
    ) => console.log(req.sessionID),
);

app.listen(app.get('port'), () => console.log('Server on'));

morgan

서버로 들어오는 클라이언트들의 로그를 남긴다.

어떤 ip로 들어왔는지까지는 안남기고 fastapi초럼 시간, 어디로 들어왔는지 이 정도만 남긴다.

morgan()안에 파라미터로 어떤 환경인지 넣으면 된다.

 

static

static 미들웨어는 spring의 resources와 같은 역할을 한다.

실제 서버에 있는 정적 파일들(html, css)등의 파일들을 해당 경로로 제공한다.

 

cookie-parser

cookie-parser는 요청에 들어있는 쿠키들을 조회해서 req.cookies 객체로 만들어준다고 한다.

아래와 같이 사용한다고 한다.

// 미들웨어 등록 시 비밀키를 넣어줍니다.
app.use(cookieParser('my_secret_key_1234'));

// 암호화된 쿠키 설정
app.get('/set-signed-cookie', (req, res) => {
    res.cookie('user_id', '12345', {
        maxAge: 60 * 60 * 1000, // 1시간
        httpOnly: true,
        signed: true // <-- 암호화 옵션 활성화
    });
    res.send('암호화된 쿠키 설정 완료!');
});

// 암호화된 쿠키 읽기
app.get('/get-signed-cookie', (req, res) => {
    // 암호화된 쿠키는 req.cookies 대신 req.signedCookies로 읽습니다.
    const userId = req.signedCookies.user_id;

    if (userId) {
        res.send(`인증된 유저 ID: ${userId}`);
    } else {
        res.send('쿠키가 없거나 위변조되었습니다.');
    }
});

 

미들웨어의 특성

미들웨어를 사용해보니, 일단 (req, res, next) 이렇게 오는 것을 볼 수 있었다.

next를 사용하면 다른 미들웨어로 보내주는거고, next를 사용하지 않는다면 send와 같은 마무리 함수를 호출해줘야 했다.

 

next에 인수를 넣을 수도 있다.

만약 인수를 넣는다면, 그 문자열의 다음 라우터 미들웨어로 이동한다.

또한 req.{이름} 등으로 req안에 데이터를 넣어둘 수도 있다.

 

multer

기존에는 json과 같은 데이터만 받았지만, 이번에는 multipartFile을 받아보도록 하자.

nest에서도 많이 봤던 친구라 익숙하다.

import express from 'express';
import morgan from 'morgan';
import cookieParser from 'cookie-parser';
import session from 'express-session';
import dotenv from 'dotenv';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import path from 'node:path';
import multer from 'multer';
import fs from 'node:fs';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

dotenv.config();

const uploadDir = path.join(__dirname, 'public/uploads');
if (!fs.existsSync(uploadDir)) {
    fs.mkdirSync(uploadDir, { recursive: true });
}
const upload = multer({
    storage: multer.diskStorage({
        destination(req, file, cb) {
            cb(null, uploadDir);
        },
        filename(req, file, cb) {
            const ext = path.extname(file.originalname);
            cb(null, `${Date.now()}${ext}`);
        },
    }),
    limits: {
        fileSize: 5 * 1024 * 1024,
    },
});
const app = express();
app.set('port', process.env.PORT || 3000);

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 || 'secret'));
app.use(
    session({
        resave: false,
        saveUninitialized: false,
        secret: process.env.SESSION_SECRET || 'secret',
        cookie: {
            httpOnly: true,
            secure: false,
        },
        name: 'session',
    }),
);

app.use((req, res, next) => {
    console.log('미들웨어 지나갑니다.');
    next();
});

app.post("/file", upload.single("file"), (req, res) => {
    console.log(req.file);
    res.send(req.file);
})

app.listen(app.get('port'), () => console.log('Server on'));

이렇게 postman으로 파일을 업로드해보니 잘 되는 것을 볼 수 있다.

 

파일을 제외한 나머지 정보는 req.body에서 가져오면 된다.

 

Router로 라우팅 주소 지정하기

그냥 이거도 fastapi에서의 라우터이다.

당연히 도메인바다 라우팅을 분리해야 하니, 라우터를 사용해 주소를 분리해보자.

우선 아래와 같이 router를 만들고

import express from 'express';

export const router = express.Router();

router.get('/', (req, res) => res.send('user'));
import express from 'express';
import dotenv from 'dotenv';
import { router as userRouter } from './routers/user.js';

dotenv.config();

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

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

app.listen(app.get('port'), () => console.log('Server on'));

이건 이제 가장 상위의 app.js이다.

 

이렇게 app.use로 라우터를 넣어주고, 그 경로를 지정하면 거기로 들어가는 요청이 다시 앞에 띠고 저 라우터로 들어간다.

이렇게 나오게 된다.

여기서 query, path param을 사용해보자.

다음과 같이 user.ts를 수정하고

import express from 'express';

export const router = express.Router();

router.get('/:id', (req, res) => {
    console.table(req.params);
    console.table(req.query);
    res.send('Hello Seungkyu');
});

 

여기에 path부터 테스트를 해보면

이렇게 나오고

 

다시 path variable을 빼고

import express from 'express';

export const router = express.Router();

router.get('/', (req, res) => {
    console.table(req.params);
    console.table(req.query);
    res.send('Hello Seungkyu');
});

다시 아래와 같이 요청해보면 잘 나오는 것을 볼 ㅜㅅ 있다.

 

express 세상의 req, res

계속 사용하는 req, res는 저번에 보았던 http를 다듬은 것이다.

많이 사용하는 것들만 대충 일단 표로 알아보고 가자.

req.app 맨날 사용하는 app 객체 자체를 접근 가능하다.
req.body body-parser가 만드는 request의 본문이 담겨있다.
req.cookies cookie-parser가 만드는 cookie의 객체가 담겨있다.
req.ip 클라이언트의 ip 주소가 담겨있다.
req.params path variable의 정보가 담겨있다.
req.query 쿼리 파라미터의 정보가 담겨있다.
req.signedCookies 서명된 쿠키들이 담겨있다.
req.get('헤더') 특정 헤더를 가져온다.

 

res.app 이거도 app 객체 자체에 접근한다.
res.cookie(key, value, option) 응답에 쿠키를 넣어서 보낸다.
res.clearCookie(key, value, option) 해당 쿠키를 제거한다.
res.end() 데이터없이 그냥 응답만 보낸다.
res.json(value) json 형식으로 응답을 보낸다.
res.redirect(주소) 해당 주소로 리다이렉트 응답을 보낸다.
res.send(value) 데이터를 넣어서 응답을 보낸다.
res.set(header, value) 응답의 헤더를 설정한다.
res.staus(httpStatus) http 코드를 지정한다.
res.sendFile(경로) 해당 경로의 파일로 응답한다.

'Node > Node.js' 카테고리의 다른 글

express로 mongodb 사용하기  (0) 2026.06.27
express로 pg 사용하기  (0) 2026.06.27
노드 패키지 매니저  (0) 2026.06.24
노드로 http 서버 만들어보기  (0) 2026.06.23
노드 사용해보기  (0) 2026.06.22
728x90

자바의 maven, 파이썬의 pypi 역할을 하는 노드의 패키지 매니저를 알아보자.

 

package.json

package.json을 통해 노드의 모듈들을 관리한다.

자바에서의 build.gradle이라고 생각하면 된다.

터미널에서

npm init

이렇게 npm을 시작하면

이렇게 화면이 나오는데, 각 항목별로 내용을 만들고 'yes'를 입력하면 된다.

 

여기에 이제 필요한 패키지를 npm을 통해서 설치하고 싶으면 다음과 같이 터미널에 입력하면 된다.

npm install [패키지]

그러면 package.json에 이렇게 패키지 이름이 추가된다.

패키지 버전

저기 위에 express와 숫자로 ^5.2.1이 적혀있는 것을 볼 수 있다.

뒤에 5.2.1은 버전이며, ^ 기호는 minor 버전까지 업데이트 된 것을 가져오고 major 버전은 유지하라는 뜻이다.

 

 

'Node > Node.js' 카테고리의 다른 글

express로 pg 사용하기  (0) 2026.06.27
express 시작하기  (0) 2026.06.27
노드로 http 서버 만들어보기  (0) 2026.06.23
노드 사용해보기  (0) 2026.06.22
노드 시작하기  (0) 2026.06.21
728x90

요청과 응답

서버는 요청이 있어야, 그 요청에 대한 응답을 전송한다.

보통의 웹은 http로 요청, 응답을 주고 받기에 http 모듈을 한 번 사용해보자.

import * as http from 'http';

http.createServer((req: any, res: any) => {
  res.write('hello seungkyu');
  res.end();
}).listen(
  8080,
  () => {
    console.log('Server is running on http://localhost:8080');
  }
)

이렇게 하고 실행하면, 다음과 같은 내용이 콘솔로 나오게 된다.


그러고 웹으로 해당 포트로 요청을 보내면, 아래와 같은 응답이 오게 된다.

 

listen 함수의 첫번째 파라미터에 포트를 넣고, res를 통해서 해당 응답에 무엇을 보낼지 입력하면 된다.

 

이거로 파일로 저장된 html을 보내는 것도 가능하다.

<!DOCTYPE html>
<html>
  <p>Hello Seungkyu</p>
</html>

이렇게 그냥 대충 만들어서

 

import * as http from 'http';
import * as fs from 'fs';

http.createServer(async (req: any, res: any) => {
  const data = await fs.readFileSync('server2.html');
  res.write(data);
  res.end();
}).listen(
  8080,
  () => {
    console.log('Server is running on http://localhost:8080');
  }
)

이렇게 data로 읽어두고, 요청이 오면 그거 보내도록 만들었다.

 

그러면 p 태그 안에 내용이 그냥 그대로 나오는 것을 볼 수 있다.

 

쿠키와 세션 이해하기

REST api에는 슬픈 부분이 있다.

상태를 유지하지 않기 때문에, 사용자가 가지고 있던 정보를 유지할 수가 없다.

그렇기에 사용하는 것이 쿠키와 세션이다.

쿠키는 서버에서 요청에 응답 할 때, 사용자를 구별? 기억?하기 위해서 어느정도 넣어두는 정보로 키-밸류의 구조로 이루어져있다.

import * as http from 'http';

http.createServer(async (req: any, res: any) => {
  console.log(req.url, req.headers.cookie);

  res.writeHead(200, {'Set-Cookie': 'name=seungkyu'});
  res.end('name cookie');
}).listen(
  8080,
  () => {
    console.log('Server is running on http://localhost:8080');
  }
)

이렇게 header에 Set-Cookie로 key=value를 지정해주면 된다.

쿠키가 잘 들어갔는지는

개발자 도구를 통해 확인이 가능하다.

일단 이렇게 쿠키를 넣을 수 있지만, 클라이언트가 직접 확인도 가능하기에 민감한 정보를 넣으면 안된다.

 

세션도 사용자의 정보를 저장해두지만, 세션은 클라이언트의 브라우저가 아닌 서버측에 저장한다는 점이 쿠키와는 다르다.

뭐...그렇다....

자세한거는 나중에 알아보자.

굳이 직접 구현은 안할 거 같고 npm 라이브러리 찾아봐야지....

 

https, http2

https는 예전에 배포하면서 굉장히 많이 사용했었다.

import * as https from 'https';
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';

// ESM(import) 환경에서 현재 파일의 디렉터리 경로를 가져오는 방법입니다.
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const options = {
  // 현재 nodejs 폴더에서 한 단계 위(..)에 있는 인증서 파일을 절대 경로로 안전하게 찾습니다.
  key: fs.readFileSync(path.join(__dirname, '..', 'localhost+2-key.pem')),
  cert: fs.readFileSync(path.join(__dirname, '..', 'localhost+2.pem'))
};

https.createServer(options, (req: any, res: any) => {
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
  res.end('<h1>🎉 HTTPS 로컬 서버 연결 성공!</h1><p>자물쇠 표시가 잘 뜨나요?</p>');
}).listen(443, () => {
  console.log('서버가 현재 https://localhost:443 에서 실행 중입니다.');
});

이렇게 pem 인증서가 있다면 443포트로 https 인증서를 사용 할 수 있게 된다.

http2는 요청 응답을 좀 묶어서 보내는 속도가 개선된 버전으로, 이 친구도 http2 모듈을 가져오면 그냥 그대로 사용이 가능하다.

import * as http2 from 'http2';
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';

// ESM(import) 환경에서 현재 파일의 디렉터리 경로를 가져오는 방법입니다.
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const options = {
  // 현재 nodejs 폴더에서 한 단계 위(..)에 있는 인증서 파일을 절대 경로로 안전하게 찾습니다.
  key: fs.readFileSync(path.join(__dirname, '..', 'localhost+2-key.pem')),
  cert: fs.readFileSync(path.join(__dirname, '..', 'localhost+2.pem'))
};

http2.createSecureServer(options, (req: any, res: any) => {
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
  res.end('<h1>🎉 HTTPS 로컬 서버 연결 성공!</h1><p>자물쇠 표시가 잘 뜨나요?</p>');
}).listen(443, () => {
  console.log('서버가 현재 https://localhost:443 에서 실행 중입니다.');
});

 

cluster

클러스터라고 하니, 도커의 클러스터링이 바로 생각난다.

원래는 노드가 싱글 프로세스로 동작했지만, 노드 프로세스를 여러개로 만들어서 요청을 받고 그거를 처리하도록 하는 방식이다.

import cluster from "node:cluster";
import os from "node:os";
import http from "node:http";

const cpuCnt = os.cpus().length;

if (cluster.isPrimary) {
    console.log(`Primary PID: ${process.pid}`);

    for (let i = 0; i < cpuCnt; i++) {
        const worker = cluster.fork();
        console.log(`Forked Worker PID: ${worker.process.pid}`);
    }

    cluster.on("online", (worker) => {
        console.log(`Worker ${worker.process.pid} is online`);
    });

    cluster.on("listening", (worker, address) => {
        console.log(
            `Worker ${worker.process.pid} listening on ${address.address}:${address.port}`
        );
    });

    cluster.on("exit", (worker) => {
        console.log(`Worker ${worker.process.pid} died`);
    });
} else {
    console.log(`Worker started: ${process.pid}`);

    http.createServer((req, res) => {
        res.end(`Handled by ${process.pid}`);
    }).listen(8080);
}

이런 식으로 하면, 뭐.... 할 수 있다는데 할지는 잘 모르겟다.

아마 하더라도 도커로 클러스터링 할 거 같아서...

'Node > Node.js' 카테고리의 다른 글

express로 pg 사용하기  (0) 2026.06.27
express 시작하기  (0) 2026.06.27
노드 패키지 매니저  (0) 2026.06.24
노드 사용해보기  (0) 2026.06.22
노드 시작하기  (0) 2026.06.21
728x90

import, export

코드를 하나의 파일에 쭉 모두 작성 할 수는 없을 것이다.

공통으로 사용하는 코드는 별도로 모듈화 해두는 것이 좋은 방법이다.

 

우선 상수들을 정의할 constants.ts를 만들어서 이름을 작성한다.

export const myName: string = "seungkyu";

외부에서도 사용 할 수 있도록 export 키워드를 사용해준다.

 

이러고 함수들을 정의할 commonFunction.ts를 다음과 같이 작성한다.

import {myName} from "./constants";

export function printName(): void{
    const logString: string = `my name is ${myName}`
    console.log(logString);
}

여기에서도 함수를 외부에서도 사용 할 수 있도록 export를 사용해주고, constants에서 myName을 import해왔다.

 

마지막으로 이 함수를 실행할 index.ts를 작성해서 실행해보자.

import {printName} from "./commonFunction";

printName();

이렇게 외부의 함수, 변수를 사용하는 것을 볼 수 있었다.

 

노드 내장 객체

globalThis

진짜 가장 위에 있는 글로벌 객체이다.

console.log(globalThis);

이렇게 출력을 해보면 다음과 같은 객체가 나오는 것을 볼 수 있다.

 

console

콘솔도 노드의 기본 객체 중 하나이다.

생각해보면 console.log() 이렇게 객체에서 함수를 사용했던 것 같다.

실제로 console 객체를 찍어보면 다음과 같이 나온다.

console.log(console);

대표적인 친구들을 살펴보면

 

  • console.time(라벨)

console.timeEnd(라벨)과 매칭하여 이 라벨끼리의 시간을 측정해서 콘솔에 남겨준다.

 

  • console.log(내용)

가장 많이 사용하는 콘솔 함수로, 그냥 콘솔에 로그를 남겨준다.

 

  • console.error(에러 내용)

에러가 발생하면 빨간색으로 로그를 좀 남겨준다.

 

  • console.table(배열)

배열을 테이블 형식으로 보기 편하게 콘솔에 남겨준다.

 

타이머

저번에 봤던 setTime~ 이런 친구들이다.

대표적으로 아래와 같은 친구들이 있다.

  • setTimeout(콜백 함수, 시간)

해당 시간 뒤에 콜백 함수를 실행한다.

  • setInterval(콜백 함수, 시간)

해당 시간 마다 콜백 함수를 실행한다.

  • setImmediate(콜백 함수)

해당 함수를 즉시 실행한다.

  • clearTimeout(setTimeout을 담은 변수)

해당 setTimeout의 실행을 취소한다.

  • clearInterval(setInterval을 담은 변수)

해당 setInterval의 실행을 취소한다.

  • clearImmediate(setImmediate를 담은 변수)

해당 setImmediate의 실행을 취소한다.

 

__filename, __dirname

해당 파일의 이름이나, 해당 파일이 어디 위치에 있는지를 출력한다.

console.log(__filename);
console.log(__dirname);

 

process

process 객체는 현재 실행되는 프로세스의 정보를 담고 있다.

뭐 이런 식으로 많은 정보들이 있는데, 가장 많이 쓰는거는 역시 env로 환경변수 가져올 때 많이 쓰던거로 기억한다.

 

노드 내장 모듈 사용하기

os

많이 사용하지는 않지만, 이런게 있다고는 한다.

import * as os from "node:os";

console.log(os.arch());
console.log(os.cpus());
console.log(os.platform());

그냥 뭐... 이런게 나온다.

 

url

인터넷 url을 쉽게 조작할 수 있도록 도와주는 모듈이다.

https://github.com/Seungkyu-Han?tab=repositories

 

Seungkyu-Han - Overview

100년 뒤 개발자 한승규입니다. Seungkyu-Han has 49 repositories available. Follow their code on GitHub.

github.com

내 깃허브 주소를 가지고 한 번 코드를 작성해보자.

const github = 'https://github.com/Seungkyu-Han?tab=repositories';

const githubUrl = new URL(github);

console.log(githubUrl.searchParams);

이렇게 url을 쉽게 조작할 수 있다.

 

이거를 querystring을 사용하면 더욱 편하게 조작이 가능하다.

const github = 'https://github.com/Seungkyu-Han?tab=repositories';

const query_strings = querystring.parse(github);

console.log(query_strings);

사실 nest를 쓰면서 이렇게 직접 쿼리를 파싱했던 기억은 없다...

 

crypto

비밀번호 암호화 할 때 사용하는 친구이다.

복호화가 불가능한 단방향 암호화, 복호화가 가능한 양방향 암호화가 존재한다.

import * as crypto from "node:crypto";

console.log(`base64: ${crypto.createHash('sha512').update('password').digest('base64')}`);
console.log(`hex: ${crypto.createHash('sha512').update('password').digest('hex')}`);
console.log(`base64: ${crypto.createHash('sha512').update('password1').digest('base64')}`);

여기서 등장하는 함수들을 살펴보면

  • createHash

사용할 해시 알고리즘을 넣어서 암호화하는 인스턴스를 만들어준다.

  • update

암호화하려는 문자열을 넣어준다.

  • digest

해당 Hash를 문자열로 인코딩 해준다.

 

이번에는 양방향 암호화 알고리즘이다.

양방향 알고리즘으로 만들면, 복호화하여 원래 비밀번호를 찾을 수 있다.

import * as crypto from "node:crypto";

const algorithm = 'aes-256-cbc';
const key = 'abcdefghijklmnopqrstuvwxyz123456';
const iv = '1234123412341234'

const cipher = crypto.createCipheriv(algorithm, key, iv);
let result = cipher.update('password', 'utf-8', 'base64');
result += cipher.final('base64');
console.log('암호화한 비밀번호: ' + result);

const decipher = crypto.createDecipheriv(algorithm, key, iv);
let originalPassword = decipher.update(result, 'base64', 'utf-8');
originalPassword += decipher.final('utf-8');
console.log('원래 비밀번호:' + originalPassword);

  • crypto.createCipheriv

암호화 알고리즘과 키, iv를 넣는다.

키는 32바이트여야 하고, iv는 16바이트여야 한다.

iv는 암호화 알고리즘을 사용 할 때의 초기화 벡터를 의미한다.

  • cipher.update

암호화할 대상과 대상의 인코딩, 출력 결과물의 인코딩을 넣는다.

  • cipher.final

출력 결과물의 인코딩을 넣어서 암호화를 완료한다.

  • crypto.createDecipheriv

복호화 할 때 사용한다.

당연히 암호화 할 때 넣었던 parameter들을 그대로 넣어줘야 한다.

  • decipher.update

암호화된 문장, 그 문장의 인코딩을 넣는다.

  • decipher.final

복호화 결과물의 인코딩을 넣는다.

 

util

그냥 잡다한 친구들이 많은 모듈인데, 이 중 deprecated라는 함수는 나중에 라이브러리 만들 때 사용 할 거 같아서 사용해보자.

이렇게하면 콘솔에 deprecate 되었다는 경고가 나오게 된다.

import * as util from "node:util";

const isDeprecated = util.deprecate((a: number, b: number) => {
    console.log(a * b)
}, 'i am deprecated')

isDeprecated(1, 2)

worker_threads

뭔가 시스템 프로그래밍 때 많이 썼던 친구 같다...

import { isMainThread, parentPort, Worker } from "node:worker_threads";

if (isMainThread) {
    // 1. 현재 파일을 워커 스레드로 실행
    const worker = new Worker(__filename);

    // 4. 워커 스레드로부터 메시지를 받았을 때 처리
    worker.on('message', (message) => {
        console.log('From worker_thread:', message);

        // 메시지를 받았으니 워커를 안전하게 종료시킵니다.
        worker.terminate();
    });

    // 5. 워커가 종료되었을 때 실행
    worker.on('exit', () => console.log('worker:exit'));

    // 2. 워커 스레드로 메시지 전송
    worker.postMessage('hello');
}
else {
    // 3. 메인 스레드로부터 메시지를 기다림
    parentPort?.on('message', (message) => {
        console.log('Worker received:', message);

        // 메인 스레드로 답장 보내기
        parentPort?.postMessage(`Hello! I received your "${message}".`);
    });
}

그냥 이런 식으로 스레드들을 조작 할 수 있다... 정도로만 알고 넘어가자.

 

파일 시스템

import * as fs from "node:fs";

fs.readFile('./README.md', 'utf8', (err, data) => {
    if (err) throw err;
    console.log(data);
})

이렇게 fs 모듈을 사용해서 해당 서버의 파일에 접근이 가능하다.

'Node > Node.js' 카테고리의 다른 글

express로 pg 사용하기  (0) 2026.06.27
express 시작하기  (0) 2026.06.27
노드 패키지 매니저  (0) 2026.06.24
노드로 http 서버 만들어보기  (0) 2026.06.23
노드 시작하기  (0) 2026.06.21
728x90

노드의 핵심 이해하기

뭔가 사람들이 하도 노드서버, 노드서버 하기에 뭔가 Node.js 자체가 백엔드 서버의 이미지를 가지고 있다.

하지만 Node.js는 그냥 단지 javascript 런타임이다.

서버

우선 우리가 만들 서버는 인터넷 상에서 클라이언트의 요청에 대해 응답을 보내주는 프로그램을 말한다.

클라이언트는 폰, 컴퓨터, 노트북 등이 될 수 있으며 대표적으로 그냥 웹이 있다.

런타임

노드가 자바스크립트의 런타임이라고 했는데, 런타임은 특정언어(javascript, python)로 만든 프로그램을 실행할 수 있는 환경을 마한다.

자바스크립트로 코드를 만들고 node로 그거를 실행한다고 생각하면 된다.

여기서 libuv는 이벤트와 관련된 일들을 처리한다.

 

이벤트 기반

이벤트 기반은 무언가 이벤트가 발생 했을 때, 등록해둔 콜백 함수를 실행하도록 만드는 것을 말한다.

대표적인 이벤트는 네트워크가 있으며, 서버는 네트워크 input 이벤트가 발생하면 그거를 처리하는 콜백을 통해 사용자에게 응답을 전송한다.

 

대부분은 그냥 이렇게 평범하게 순서대로 동작한다.

function a(): void{
    b();
    console.log("I'm a")
}

function b(): void{
    c();
    console.log("I'm b");
}

function c(): void{
    console.log("I'm c");
}

a();

왜 이렇게 실행되는지는 호출스택을 그려보면 바로 알것이다.

 

여기에 시간이 지나면 실행되는 이벤트를 넣어보자.

function timer(){
    console.log("I'm a timer");
}

console.log("start");
setTimeout(timer, 2000);
console.log("end");

이거는 setTimeout이 이벤트 루프에 넣어두기 때문이다.

 

 

논 블로킹 I/O

이런 이벤트 루프는 보통 I/O 작업을 처리하기 위해 사용한다.

I/O는 입력과 출력을 말하는데, 대표적으로 데이터베이스의 접근이 있다.

Spring MVC는 데이터베이스에 접근 할 때, 해당 쓰레드가 Blocking이 되어 버린다.

굳이 한참 뒤에나 완료될 작업을 대기 할 필요가 있을까?

그냥 완료되면 알아서 가져오면 되지 않을까?

싱글 스레드

node 서버에서 논 블로킹만큼 많이 언급되는 것이 싱글 스레드이다.

하지만 node는 엄격하게 말하면 싱글 스레드는 아니고, 직접 제어할 수 있는 스레드가 하나인 것이라고 한다.

'Node > Node.js' 카테고리의 다른 글

express로 pg 사용하기  (0) 2026.06.27
express 시작하기  (0) 2026.06.27
노드 패키지 매니저  (0) 2026.06.24
노드로 http 서버 만들어보기  (0) 2026.06.23
노드 사용해보기  (0) 2026.06.22

+ Recent posts