[Node.js] Express, TypeScript, MongoDB ํšŒ์›๊ฐ€์ž… (1)

2021. 6. 18. 17:08ยทDEV/Node.js

๐Ÿ’ป ์ฝ”๋“œ

https://github.com/jokj624/authCRUD-TS

 

jokj624/authCRUD-TS

Node js, Express, MongoDB, TypeScript ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ CRUD ๊ตฌํ˜„ ์—ฐ์Šต - jokj624/authCRUD-TS

github.com

๐Ÿค” why?

๊ณง .. ๋‹ค๊ฐ€์˜ฌ ์•ฑ์žผ์„ ์••๋‘๊ณ  ์„œ๋ฒ„ ๋งํ•˜๋Š” ๊ฐ์ž์ธ ๋‚ด๊ฐ€ ์กฐ๊ธˆ์ด๋ผ๋„ ๊ณต๋ถ€๋ฅผ ํ•ด์•ผ๊ฒ ๋‹ค ํ•˜๊ณ  ๋กœ๊ทธ์ธ/ํšŒ์›๊ฐ€์ž…์„ 3 Layer Architecture ๋กœ ์„ค๊ณ„ํ•ด๋ณด์ž ํ•˜๊ณ  ๊ณต๋ถ€๋ฅผ ์‹œ์ž‘ํ–ˆ๋‹ค.

์›๋ž˜๋Š” api ๋‚ด์— ๋ผ์šฐํŠธ, ์ปจํŠธ๋กค๋Ÿฌ, ์„œ๋น„์Šค ๋กœ์ง์„ ๋‹ค ๋„ฃ์–ด๋†“๋Š” ์‹์œผ๋กœ ๊ตฌํ˜„ํ–ˆ๋Š”๋ฐ ์ œ๋Œ€๋กœ ์„ค๊ณ„ํ•ด๋ณด๊ณ  ์‹ถ๋‹ค. 

๋งŽ์€ ๋ธ”๋กœ๊ทธ์™€ SOPT ์„ธ๋ฏธ๋‚˜์—์„œ ํ•œ ๋‚ด์šฉ, github๋“ค์„ ์ฐธ๊ณ ํ•ด์„œ ํ•ด๋ณด์•˜๋‹ค.

์•„์ง ์ฝ”๋“œ๋ฅผ ์กฐ๊ธˆ ๋” ๋ฆฌํŒฉํ† ๋ง ํ•ด์•ผํ•˜์ง€๋งŒ ๊ฐ„๋‹จํ•œ ๊ตฌํ˜„ ๋‚ด์šฉ๋“ค์„ ์ •๋ฆฌํ•  ๊ฒธ, ๊ธ€์„ ์ž‘์„ฑํ•ด๋ณธ๋‹ค.

 

โœ”๏ธ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ์™€ DB ์„ค๊ณ„

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

src _____________ Loaders _______ db.ts
nodemon.json   |_ api  ___________________ middleware _ auth.ts
package.json   |                        |_ route ______ index.ts                             
tsconfig.json  |                                     |_ UserRouter.ts    
               |_ config __ index.ts
               |_ models __ User.ts                  
               |_ interfaces __ IUser.ts
               |_ errors _________________ errorGenerator.ts 
               |                        |_ generalErrorHandler.ts 
               |_ controllers ____________ index.ts
               |                        |_ UserContorller.ts
               |_ service ________________ inedx.ts
               |                        |_ UserService.ts
               |_ index.ts

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ๋Š” 3๊ณ„์ธต ์„ค๊ณ„์— ๋งž๊ฒŒ Route => Controller => Service ์ž์‹ ์˜ ๋ฐ”๋กœ ํ•˜๋‹จ ๊ณ„์ธต์—๋งŒ ์˜์กดํ•˜๋„๋ก ์„ค๊ณ„ํ–ˆ๋‹ค.

์ตœ๋Œ€ํ•œ ๋ชจ๋“ˆํ™” ์‹œ์ผœ๋ณด์•˜๋‹ค.

User DB

src/models/user.ts

import mongoose from "mongoose";
import { IUser } from "../interfaces/IUser";

const UserSchema = new mongoose.Schema({
    name: {
        type: String,
    },
    email: {
        type: String,
        unique: true,
    },
    password: {
        type: String,
    },
    avatar: {
        type: String,
    },
    date: {
        type: Date,
        default: Date.now,
    },
});

export default mongoose.model<IUser & mongoose.Document>("User", UserSchema);

์—ฐ์Šต์ด๋‹ˆ ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ์ด๋ฆ„, ์ด๋ฉ”์ผ, ํŒจ์Šค์›Œ๋“œ, ์•„๋ฐ”ํƒ€(๊ธฐ๋ณธ ํ”„๋กœํ•„ ์‚ฌ์ง„), ๊ฐ€์ž… ๋‚ ์งœ๋กœ ์„ค๊ณ„ํ–ˆ๋‹ค.

Interface

src/interfaces/IUser.ts

export interface IUser{
    name: string;
    email: string;
    password: string;
    avatar: string;
    date: Date;
}

export interface IUserInputDTO {
    name: string;
    email: string;
    password: string;
    avatar?: string;
    date?: Date;
}

export interface userUniqueSearchInput {
    email : string;
}

user ์ธํ„ฐํŽ˜์ด์Šค์—์„œ๋Š” ํ›„์— post ํ•  ๋•Œ ์‚ฌ์šฉํ•  DTO๋ฅผ ๋งŒ๋“ค์–ด ์ฃผ์—ˆ๋‹ค. 

InputDTO๋Š” ํšŒ์›๊ฐ€์ž…์‹œ, Search๋Š” ์ค‘๋ณต ์ด๋ฉ”์ผ์„ ์ฐพ์„ ๋•Œ ์‚ฌ์šฉํ•  ์ธํ„ฐํŽ˜์ด์Šค 

์—ฌ๊ธฐ์„œ๋Š” ์ด๋ฉ”์ผ๋งŒ์œผ๋กœ ์ฐพ์•˜์ง€๋งŒ 

email?, name? ๊ฐ™์ด ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•ด์„œ name์œผ๋กœ ์ฐพ์•„๋„ ๋œ๋‹ค.

โœ”๏ธ Router

src/api/route/index.ts

import { Router } from 'express';
import UserRouter from './UserRouter'; 

const router = Router();

router.use('/users', UserRouter);

export default router;

UserRouter ๋ชจ๋“ˆ์—์„œ ์ง์ ‘์ ์ธ ๋ผ์šฐํŒ…์„ ํ•  ๊ฒƒ ์ด๋ฏ€๋กœ UserRouter ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ •์˜ํ•ด์ค€๋‹ค.

 

src/api/route/UserRouter.ts

import { Router } from 'express';
import { UserController } from '../../controllers';

const router = Router();

router.use('/signup', UserController.signUp);
router.use('/login', UserController.logIn);

export default router;
 

 ํšŒ์›๊ฐ€์ž…(signUp), ๋กœ๊ทธ์ธ(logIn)์— ์‚ฌ์šฉํ•  ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ •์˜ํ•ด์ค€๋‹ค.

๋ชจ๋“  ์ฒ˜๋ฆฌ๋Š” UserController ๋ชจ๋“ˆ ๋‚ด ํ•จ์ˆ˜์—์„œ ์ง„ํ–‰ํ•œ๋‹ค.

๊ถ๊ธˆํ•œ์ ์ด ์žˆ๋Š”๋ฐ ์ € router.use ์—์„œ use๋ฅผ post, get ๊ฐ™์€ HTTP Method๋กœ ํ‘œ์‹œํ•ด๋„ ๋˜๋Š” ๊ฑด๊ฐ€?

์ฐพ์•„๋ด์•ผ ๊ฒ ๋‹ค.

โœ”๏ธ Controller

src/contorllers/index.ts

import UserController from './UserController';
// ๋ชจ๋“ˆ์„ ํŒจํ‚ค์ง€ํ™” ํ•˜์—ฌ ๊ฐ์ฒด๋กœ ๋‚ด๋ณด๋‚ธ๋‹ค.
export{
    UserController  //(= UserController : UserController)
}

UserContorller ๋ฅผ ํŒจํ‚ค์ง€ํ™” ํ•˜์—ฌ export ํ•œ๋‹ค.

 

src/controllers/UserContorller.ts

import express, { NextFunction, Request, Response } from "express";
import bcrypt from "bcryptjs";
import jwt from "jsonwebtoken";
import config from "../config";
import errorGenerator from "../errors/errorGenerator";
import gravatar from 'gravatar';
import { check, validationResult } from "express-validator";
import { IUserInputDTO } from "../interfaces/IUser";
import { UserService } from "../services";
import { nextTick } from "process";

const signUp = async (req: Request, res: Response, next: NextFunction) => {
    check("name", "Name is required").not().isEmpty();
    check("email", "Please include a valid email").isEmail();
    check("password", "Please enter a password with 6 or more characters").isLength({ min: 6 });
    const { name, email, password } : IUserInputDTO = req.body;
    try{
        const errors = validationResult(req.body);
        if(!errors.isEmpty()){
            return res.status(400).json({ errors: errors.array() });
        }

        const foundUser = await UserService.findEmail({ email });
        if(foundUser)   errorGenerator({ statusCode: 409 });  // ์ด๋ฏธ ๊ฐ€์ž…ํ•œ ์œ ์ €

        const avatar = gravatar.url(email, {
            s: "200",
            r: "pq",
            d: "mm",
        });

        const salt = await bcrypt.genSalt(10);
        const hashedPassword = await bcrypt.hash(password, salt);

        const createdUser = await UserService.createUser({ name, email, password: hashedPassword, avatar: avatar });

        const payload = {
            user: {
                email: createdUser.email,
            },
        };
        jwt.sign(
            payload,
            config.jwtSecret,
            { expiresIn: 36000 },
            (err, token) => {
                if(err) throw err;
                res.json({ token });
            }
        );
    } catch (err) {
        next(err);
    }
};

export default {
    signUp,
    logIn
}

์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” singUp ๋งŒ ๋‹ค๋ฃฐ ๊ฑฐ๋ผ logIn ํ•จ์ˆ˜๋Š” ๋บ๋‹ค.

DB์— ์ง์ ‘ ์ ‘๊ทผํ•˜๋Š” ์ผ์€ ๋ชจ๋‘ Service์—์„œ ๊ตฌํ˜„ ํ•  ๊ฒƒ์ด๋‹ค.

    check("name", "Name is required").not().isEmpty();
    check("email", "Please include a valid email").isEmail();
    check("password", "Please enter a password with 6 or more characters").isLength({ min: 6 });
    const { name, email, password } : IUserInputDTO = req.body;
    try{
        const errors = validationResult(req.body);
        if(!errors.isEmpty()){
            return res.status(400).json({ errors: errors.array() });
        }

        const foundUser = await UserService.findEmail({ email });
        if(foundUser)   errorGenerator({ statusCode: 409 });  // ์ด๋ฏธ ๊ฐ€์ž…ํ•œ ์œ ์ €

check๋Š” express-validator ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์กด์žฌํ•˜๋Š” ํ•จ์ˆ˜๋กœ, ๋‹ค์–‘ํ•œ ๊ฒ€์ฆ์„ ๋„์™€์ค€๋‹ค.

์—ฌ๊ธฐ์„œ๋Š” Name ํ•„๋“œ๊ฐ€ ๋น„์—ˆ๋Š”์ง€, ์ด๋ฉ”์ผ์ด ์ œ๋Œ€๋กœ ์กด์žฌํ•˜๋Š”์ง€, ํŒจ์Šค์›Œ๋“œ ๊ธธ์ด๊ฐ€ ์ตœ์†Œ 6 ์ด์ƒ์ธ์ง€ ๊ฒ€์‚ฌํ•˜๊ณ  ๋„˜์–ด๊ฐ„๋‹ค.

์ดํ›„ req.body์—์„œ ์ธํ„ฐํŽ˜์ด์Šค์—์„œ ์ •์˜ํ•œ DTO ํƒ€์ž…์œผ๋กœ body ๋‚ด์šฉ์„ ๊บผ๋‚ด์ค€๋‹ค.

validationResult์—์„œ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ body๋‚ด์šฉ์„ ๊ฒ€์‚ฌํ•˜๊ณ  ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ ์—๋Ÿฌ๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค.

 

์ดํ›„ ์ค‘๋ณต๋œ ์œ ์ €์ธ์ง€ ๊ฒ€์‚ฌ๋ฅผ ์œ„ํ•ด Service ๋กœ์ง์— ๊ตฌํ˜„๋˜์–ด ์žˆ๋Š” findEmail ํ•จ์ˆ˜์— email์„ ๋ณด๋‚ด์ค€๋‹ค.

๋งŒ์•ฝ ํ•ด๋‹น ์œ ์ €๊ฐ€ ์กด์žฌ ํ•  ์‹œ ์—๋Ÿฌ ํ•ธ๋“ค๋ง ํ•จ์ˆ˜์— 409 (duplicate) ์ฝ”๋“œ๋ฅผ ๋ณด๋‚ด์ค€๋‹ค.

        const avatar = gravatar.url(email, {
            s: "200",
            r: "pq",
            d: "mm",
        });

        const salt = await bcrypt.genSalt(10);
        const hashedPassword = await bcrypt.hash(password, salt);

        const createdUser = await UserService.createUser({ name, email, password: hashedPassword, avatar: avatar });

avatar๋Š” gravatar ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•ด์„œ ๊ธฐ๋ณธ ์ด๋ฏธ์ง€ ์ฃผ์†Œ๋ฅผ ๋งŒ๋“ค์–ด์ค€๋‹ค.

์ดํ›„ bcrypt๋ฅผ ํ†ตํ•ด password๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์•”ํ˜ธํ™” ํ•  ๊ฒƒ์ด๋‹ค.

์ด์ œ Service๋กœ์ง์— ์กด์žฌํ•˜๋Š” createUserํ•จ์ˆ˜๋กœ ์ด ์ •๋ณด๋“ค์„ ๋ณด๋‚ด DB์— ์ €์žฅํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

 jwt.sign(
            payload,
            config.jwtSecret,
            { expiresIn: 36000 },
            (err, token) => {
                if(err) throw err;
                res.json({ token });
            }
        );

config์— ๋ฏธ๋ฆฌ ์ •ํ•ด๋‘” jwt ์ •๋ณด๋ฅผ ์ด์šฉํ•ด ํ† ํฐ์„ ๋ฐœ๊ธ‰ํ•œ๋‹ค. 

์ด ํ† ํฐ์„ ํด๋ผ์ด์–ธํŠธํ•œํ…Œ ๋˜์ ธ์ฃผ๋ฉด ๋‹ค์šฉ๋„๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

โœ”๏ธ Service

src/services/index.ts

import UserService from './UserService';

export{
    UserService
}  // ๊ฐ์ฒด ๋‚ด๋ณด๋‚ด๊ธฐ

์ปจํŠธ๋กค๋Ÿฌ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ UserService ๋ชจ๋“ˆ์„ ๋‚ด๋ณด๋‚ธ๋‹ค.

 

src/services/UserService.ts

import { IUserInputDTO, userUniqueSearchInput } from "../interfaces/IUser";
import User from "../models/User";

const createUser = (data: IUserInputDTO) => {
    const user = new User(data);
    return user.save();
}

const findEmail = (data: userUniqueSearchInput) => {
    const { email } = data;
    return User.findOne({ email });
}
export default {
    createUser,
    findEmail
};

์•„๊นŒ ํšŒ์›๊ฐ€์ž… ์ปจํŠธ๋กค๋Ÿฌ ํ•จ์ˆ˜์—์„œ ๋ดค๋˜ ์„œ๋น„์Šค ๋กœ์ง ํ•จ์ˆ˜๋“ค์ด๋‹ค.

createUser์—์„œ๋Š” data๋ฅผ ๋ฐ›์•„ ์ƒˆ๋กœ์šด User๋ฅผ ๋งŒ๋“ค์–ด DB์— save ํ•ด์ค€๋‹ค. 

๋งŒ์ผ IUserInputDTO ์ธํ„ฐํŽ˜์ด์Šค ํƒ€์ž…๊ณผ ๋งž์ง€ ์•Š๋Š” data๊ฐ€ ์˜จ๋‹ค๋ฉด ์—๋Ÿฌ๊ฐ€ ๋‚  ๊ฒƒ์ด๋‹ค.

 

findEmail ํ•จ์ˆ˜์—์„œ๋Š” email์„ ๋ฐ›์•„ findOne ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•ด ํ•ด๋‹น ์œ ์ €๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ๊ฒ€์‚ฌํ•œ๋‹ค.

์กด์žฌํ•œ๋‹ค๋ฉด ํ•ด๋‹น ์œ ์ € ๊ฐ์ฒด๋ฅผ return ํ•  ๊ฒƒ์ด๋‹ค.

โœ”๏ธ Error ํ•ธ๋“ค๋ง

src/errors/errorGenerator.ts

const DEFAULT_HTTP_STATUS_MESSAGES = {
    400: 'Bad Requests',
    401: 'Unauthorized',
    403: 'Foribdden',
    404: 'Not Found',
    409: 'duplicate',
    500: 'Internal Server Error',
    503: 'Temporary Unavailable',
};

//interface ์ด์šฉํ•ด Error ๊ฐ์ฒด์— statusCode key ์ถ”๊ฐ€
export interface ErrorWithStatusCode extends Error {
    statusCode? : number
};

const errorGenerator = ({ msg='', statusCode=500}: { msg?: string, statusCode: number }): void => {
    //์ธ์ž๋กœ ๋“ค์–ด์˜ค๋Š” ๋ฉ”์„ธ์ง€์™€ ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๋งคํ•‘
    const err: ErrorWithStatusCode = new Error(msg || DEFAULT_HTTP_STATUS_MESSAGES[statusCode]);
    err.statusCode = statusCode;
    throw err;
}

export default errorGenerator;

์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ๋Š” ๋”ฑํžˆ ์—†์–ด๋„ ๋œ๋‹ค. ๊ทธ๋ƒฅ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์ง์ ‘ res.json์œผ๋กœ send ํ•ด์ค˜๋„ ๋œ๋‹ค.

ํ•˜์ง€๋งŒ ์ฐธ๊ณ  ๋ธ”๋กœ๊ทธ์—์„œ ํ•ธ๋“ค๋ง ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด ๋†“์œผ๋ฉด ๊ฐ„ํŽธํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹คํ•˜์—ฌ ๋‚˜๋„ ๋”ฐ๋ผ์„œ ๋งŒ๋“ค์–ด ๋ณด์•˜๋‹ค.

๋‹ค์–‘ํ•œ ์ƒํƒœ์ฝ”๋“œ๋ฅผ ๋ฏธ๋ฆฌ ์ •์˜ํ•ด๋‘๊ณ  ์ด์™€ ๋งž๋Š” ๋ฉ”์‹œ์ง€๋ฅผ error ๊ฐ์ฒด๋กœ ์ƒ์„ฑํ•˜์—ฌ ๋˜์ ธ์ค€๋‹ค.

 

src/errors/generalErrorHandler.ts

import { ErrorRequestHandler, Request, Response, NextFunction } from 'express';
import { ErrorWithStatusCode } from './errorGenerator';

const generalErrorHandler: ErrorRequestHandler = (err: ErrorWithStatusCode, req: Request, res: Response, next: NextFunction) => {
    const { message, statusCode } = err;
    //console.error(err);
    res.status(statusCode || 500).json({ message });
}

export default generalErrorHandler;

errorGenerator ์—์„œ ์ •์˜ํ•œ ์ธํ„ฐํŽ˜์ด์Šค ErrorWithStatusCode ์—๋Ÿฌ๋ฅผ ์ง์ ‘ response๋กœ ๋ณด๋‚ด์ฃผ๊ฒŒ ๋œ๋‹ค.

์—๋Ÿฌ ํ•ธ๋“ค๋ง ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ๊ฐ„ํŽธํ•˜๊ฒŒ 

   if(foundUser)   errorGenerator({ statusCode: 409 });  // ์ด๋ฏธ ๊ฐ€์ž…ํ•œ ์œ ์ €

์ด๋Ÿฐ์‹์œผ๋กœ ์ƒํƒœ ์ฝ”๋“œ๋งŒ ๋ณด๋‚ด์ฃผ๋ฉด ์•Œ์•„์„œ send ํ•ด์ค€๋‹ค.

โœ”๏ธ ๊ทธ ์™ธ ์ฝ”๋“œ๋“ค

src/index.ts

๊ฐ€์žฅ ์ฒ˜์Œ ์‹คํ–‰ ๋  ์ฝ”๋“œ์ด๋‹ค.

import express, { Express } from "express"; 
import routes from './api/route';
import generalErrorHandler from "./errors/generalErrorHandler";
const app : Express = express(); 
import connectDB from "./Loaders/db";

// Connect Database
connectDB();

app.use(express.urlencoded());
app.use(express.json()); 

app.use(routes);
app.use(generalErrorHandler);
// error handler
app.use(function (err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get("env") === "production" ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render("error");
});

app 
  .listen(5000, () => {
    console.log(`
    ################################################
    ๐Ÿ›ก๏ธ  Server listening on port: 5000 ๐Ÿ›ก๏ธ
    ################################################
  `);
  })
  .on("error", (err) => {
    console.error(err);
    process.exit(1);
  });

Loaders์— ๋งŒ๋“ค์–ด๋‘” db์—ฐ๊ฒฐ ํ•จ์ˆ˜์™€ ์„œ๋ฒ„ ์‹คํ–‰์„ ์ง„ํ–‰ํ•œ๋‹ค.

 

๐Ÿ”— ์ฐธ๊ณ  ์ž๋ฃŒ

https://velog.io/@hopsprings2/%EA%B2%AC%EA%B3%A0%ED%95%9C-node.js-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90-%EC%84%A4%EA%B3%84%ED%95%98%EA%B8%B0

 

๊ฒฌ๊ณ ํ•œ node.js ํ”„๋กœ์ ํŠธ ์„ค๊ณ„ํ•˜๊ธฐ

๋ณธ ๊ธ€์€ Sam Quinn์˜ “Bulletproof node.js project architecture” ๊ธ€์„ ๋ฒˆ์—ญํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. [Bulletproof node.js project architecture ๐Ÿ›ก๏ธ Express.js is great frameworks f

velog.io

https://velog.io/@hjulee12/Nodejs-practice.-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EB%AA%A8%EB%8D%B8%EB%A7%81-%EB%B0%8F-blog-API-%EA%B5%AC%ED%98%84

 

Node.js | User(ํšŒ์›๊ฐ€์ž…) API ๊ตฌํ˜„

Node.js - ํšŒ์›๊ฐ€์ž… API ๊ตฌํ˜„

velog.io

 

์ €์ž‘์žํ‘œ์‹œ ๋น„์˜๋ฆฌ ๋ณ€๊ฒฝ๊ธˆ์ง€ (์ƒˆ์ฐฝ์—ด๋ฆผ)

'DEV > Node.js' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

Node.js, MongoDB Change Streams ๋ฅผ ์‚ฌ์šฉํ•œ ํŠน์ • ์‚ฌ์šฉ์ž์—๊ฒŒ ํŠน์ • ์‹œ๊ฐ„์— FCM ๋ณด๋‚ด๊ธฐ  (5) 2022.01.21
[CI/CD] AWS CodeDeploy, CodePipeline ์œผ๋กœ node.js, ec2, git ๋ฐฐํฌ ์ž๋™ํ™”ํ•˜๊ธฐ  (4) 2021.12.16
Node.js , express, mongoDB, typescript ์ดˆ๊ธฐ ์„ค์ •  (0) 2021.05.26
Node.js + Koa + Typescript ๋กœ ์Šฌ๋ž™ ๋ด‡ ๊ฐœ๋ฐœํ•ด๋ณด๊ธฐ (1)  (0) 2021.04.19
Node js + Express + Socket.io ๋กœ 1๋Œ€ 1 ์ฑ„ํŒ… ๊ตฌํ˜„ํ•˜๊ธฐ (1)  (4) 2021.03.14
'DEV/Node.js' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
  • Node.js, MongoDB Change Streams ๋ฅผ ์‚ฌ์šฉํ•œ ํŠน์ • ์‚ฌ์šฉ์ž์—๊ฒŒ ํŠน์ • ์‹œ๊ฐ„์— FCM ๋ณด๋‚ด๊ธฐ
  • [CI/CD] AWS CodeDeploy, CodePipeline ์œผ๋กœ node.js, ec2, git ๋ฐฐํฌ ์ž๋™ํ™”ํ•˜๊ธฐ
  • Node.js , express, mongoDB, typescript ์ดˆ๊ธฐ ์„ค์ •
  • Node.js + Koa + Typescript ๋กœ ์Šฌ๋ž™ ๋ด‡ ๊ฐœ๋ฐœํ•ด๋ณด๊ธฐ (1)
jobchae
jobchae
๋งํ•˜๋Š” ๊ฐ์ž์ง€๋งŒ, ์ฝ”๋“œ๋ฅผ ๋„์ ์ด๋Š” Node.js ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์ž์ž…๋‹ˆ๋‹ค.
  • jobchae
    JOBCHAE
    jobchae
  • ์ „์ฒด
    ์˜ค๋Š˜
    ์–ด์ œ
    • ๐Ÿš€ JOBCHAE (182)
      • DEV (151)
        • PS (108)
        • Node.js (12)
        • React (3)
        • docker (1)
        • ์žก๋‹คํ•œ ๊ฐœ๋ฐœ ์ผ์ง€ (21)
        • injection (1)
        • JS, TS (3)
        • DB (2)
      • ์ถ•๊ตฌ (0)
      • ์ผ์ƒ (19)
      • ์˜ํ™” (3)
      • ์Œ์•… (8)
  • ๋ธ”๋กœ๊ทธ ๋ฉ”๋‰ด

    • ๐Ÿ’ป Github
    • ๐Ÿ™‹๐Ÿป Linkedin
    • ๐Ÿ“– ๋ฐฉ๋ช…๋ก
  • ๋งํฌ

    • PS Github
  • ๊ณต์ง€์‚ฌํ•ญ

  • ์ธ๊ธฐ ๊ธ€

  • ํƒœ๊ทธ

    ์šฐ์„ ์ˆœ์œ„ํ
    SOPT
    ์Šฌ๋ž™
    ์œ„์ƒ์ •๋ ฌ
    ์†ํŠธ
    DFS
    ํšŒ๊ณ 
    db
    ๋ฐฑ์ค€
    mongoDB
    nodejs
    ๊ฐœ๋ฐœ
    Express
    node.js
    ์ผ์ƒ
    boj
    ์•Œ๊ณ ๋ฆฌ์ฆ˜
    ์ด๋ถ„ํƒ์ƒ‰
    JavaScript
    DP
    typescript
    BFS
    ์•ฑ์žผ
    GitHub
    ๋ฆฌ์•กํŠธ
    react
    PS
    Nest.js
    ์Šฌ๋ž™๋ด‡
    ๋ ›์ธ ๋ฝํŽ˜์Šคํ‹ฐ๋ฒŒ
  • ์ตœ๊ทผ ๋Œ“๊ธ€

  • ์ตœ๊ทผ ๊ธ€

  • hELLOยท Designed By์ •์ƒ์šฐ.v4.10.5
jobchae
[Node.js] Express, TypeScript, MongoDB ํšŒ์›๊ฐ€์ž… (1)
์ƒ๋‹จ์œผ๋กœ

ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”