티스토리 뷰

728x90
반응형

목차

     

     

    지난 글에선 기존의 자동 증가 로직을 미들웨어로 분리하고,

     

    사용자(User)에 관한 모듈과 JWT를 이용한 회원가입, 로그인 구현을 해보았다.

     

    이번 글에서는 게시글 작성, 수정, 삭제, 그리고 사용자 정보 수정, 삭제 시

     

    로그인 정보를 가져와 검증한 뒤에 본인이 맞는 경우에만 로직이 작동하도록 하고

     

    지난 글에 언급했던 에러 처리를 통일한 뒤 유틸로 분리해서 전역에서 사용하도록 하겠다.

     

    오늘 완성될 디렉토리와 모듈 구조는 아래와 같다.

     

    새로 추가된 모듈과 수정된 모듈을 가능한 빼먹지 않고 적어보겠다.

     

    /src/middleware

     

    미들웨어에선 앞서 말했듯 로그인한 사용자 정보를 가져오기 위한 모듈을 추가했다.

     

    /authentication.ts

     

    import jwt from 'jsonwebtoken';
    import { Request, Response, NextFunction } from 'express';
    import asyncHandler from 'express-async-handler';
    import User from '../models/User';
    import sendErrorResponse from '../utils/sendErrorResponse';
    
    const authMiddleware = asyncHandler(async (req: Request, res: Response, next: NextFunction) => {
      let token;
    
      if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) {
        try {
          token = req.headers.authorization.split(' ')[1];
    
          const decoded = jwt.verify(token, process.env.JWT_SECRET!);
    
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          res.locals.user = await User.findOne({ userSeq: (decoded as any).userSeq }).select('-password');
    
          next();
        } catch (error) {
          sendErrorResponse(res, 401, 'Unauthorized, Token Failed');
          return;
        }
      }
    
      if (!token) {
        sendErrorResponse(res, 401, 'Unauthorized, No Token');
        return;
      }
    });
    
    export { authMiddleware };

    이 미들웨어 모듈은 이름대로 인증 미들웨어이다.

     

    이후의 코드에서 클라이언트의 요청과 응답 사이에 작동하며

     

    구체적으로는 JWT를 검증해 사용자의 인증 여부를 확인하는 역할을 한다.

    import jwt from 'jsonwebtoken';
    import { Request, Response, NextFunction } from 'express';
    import asyncHandler from 'express-async-handler';
    import User from '../models/User';
    import sendErrorResponse from '../utils/sendErrorResponse';

    필요한 모듈을 가져온다. 위의 네 가지는 이전 글에서 설명을 되풀이했으므로 생략한다.

     

    마지막에 가져온 모듈은 아직 존재하지 않지만 미리 설명하자면 에러 핸들링을 하나로 통일해

     

    재사용이 가능하도록 만든 유틸 모듈이다. 바로 이어서 만들 것이다.

    const authMiddleware = asyncHandler(async (req: Request, res: Response, next: NextFunction) => {});

    'authMiddleware' 미들웨어를 정의한다. 역시 asyncHandler로 감싸져 있으며 request, response, next를 인자로 받는다.

     if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) {
        try {
          token = req.headers.authorization.split(' ')[1];

    요청 헤더에 'Authorization'을 포함시키면서 'Bearer'로 시작하는 경우에만 토큰을 추출한다.

          const decoded = jwt.verify(token, process.env.JWT_SECRET!);

    JWT를 디코드 하고, 그 결과를 'decode'에 저장한다. 여기서 디코딩된 결과는

     

    지난 글에서 JWT에 사인을 할 때 입력했던 입력값들로 이루어져 있다.

          res.locals.user = await User.findOne({ userSeq: (decoded as any).userSeq }).select('-password');

    몽고DB에서 해당 사용자를 찾고, 그 정보 중 비밀번호를 제외한 정보를 'res.locals.user'에 저장한다.

     

    이제 'res.locals'는 호출한 요청의 생명주기동안 사용할 수 있는 변수를 제공한다.

          next();

    모든 과정이 성공하면 다름 미들웨어를 호출한다.

        } catch (error) {
          sendErrorResponse(res, 401, 'Unauthorized, Token Failed');
          return;
        }
      }

    만약 토큰 검증이 실패하면 에러메시지와 401 코드를 반환한다. 여기서 sendErrorResponse는 위에서 가져온

     

    모듈 안에 들어있기 때문에, 이다음에 바로 작성할 모듈이 있어야 기능한다.

      if (!token) {
        sendErrorResponse(res, 401, 'Unauthorized, No Token');
        return;
      }
    });

    추가로 만약 토큰이 존재하지 않아도 401 코드와 에러 메시지를 반환한다.

    export { authMiddleware };

    마지막으로 해당 미들웨어를 내보내 다른 곳에서도 사용할 수 있도록 한다.

     

    /src/middleware

     

    /sendErrorResponse.ts

     

    바로 이어서 에러 핸들링 방법의 통일과 추출한 모듈을 만들어보자.

    import { Response } from 'express';
    
    export default function sendErrorResponse(res: Response, statusCode: number, message: string) {
      res.status(statusCode).json({ error: message });
    }

    지난 글에서 에러를 다루는 방법은 크게 위와 같은 방법과 throw로 에러를 던지는 방법이 있다고 했었다.

     

    두 방식은 서버에 걸리는 부담도 비슷하지만 지난 글에 적었듯이 차이점이 존재했는데,

     

    그 외에도 throw를 쓰지 않는 이유에 대해서 몇 줄만 적고 넘어가겠다.

     

    위처럼 코드를 작성한 뒤 호출하고 return;을 하면 이후의 로직이 멈추고, throw도 비슷하게 기능하지만

     

    throw는 에러를 서버 쪽으로 던지기 때문에 제대로 처리가 되지 않으면 서버 자체가 뻗어버릴 가능성이 있다고 한다.

     

    그래서 주변 선배 개발자들에게 물어봐도 throw로 에러를 던지는 식으로 처리하는 경우는 없다고.

     

    따라서 위와 같이 구현하거나 아니면 try-catch문으로 잡아서 콘솔에 로그를 찍는 방법은 선호한다고 했다.

     

    해서 나중엔 어떻게 될지 모르겠지만 나는 일단 위와 같은 방식으로 에러 핸들링을 통일해 보기로 했다.

     

    코드는 굉장히 단순하다. 주어진 응답 객체, 그리고 에러 코드와 메시지를 사용해서 응답을 생성,

     

    JSON 형식으로 클라이언트에게 반환하는 로직이다.

     

    지난 글에 개별적으로 적었던 에러 핸들링 로직을 그대로 추출해 유틸 파일을 만든 것이나 다름없기 때문에 따로 설명할 것은 없다.

     

    다만, 위 미들웨어 모듈에서 볼 수 있듯이

        } catch (error) {
          sendErrorResponse(res, 401, 'Unauthorized, Token Failed');
          return;
        }
      }

    와 같이 메서드 호출 후 return;을 반드시 붙여주어야 코드 진행이 멈추게 된다.

     

    앞으로의 에러 핸들링은 전부 이 유틸 메서드를 호출해서 처리하겠다.

     

    /src/models

     

    /User.ts

     

    import mongoose, { Schema, Document } from 'mongoose';
    import { autoIncrement } from '../middleware/autoIncrement';
    import bcrypt from 'bcryptjs';
    
    interface IUser extends Document {
      userSeq: number;
      id: string;
      password: string;
      name: string;
      matchPassword: (enteredPassword: string) => Promise<boolean>;
    }
    
    const userSchema: Schema<IUser> = new Schema(
      {
        userSeq: { type: Number, unique: true, required: true, default: 1 },
        id: {
          type: String,
          unique: true,
          required: true,
          match: [/^([\w-.]+@([\w-]+\.)+[\w-]{2,4})?$/, '올바른 이메일 형식을 입력해 주세요.'],
        },
        password: {
          type: String,
          required: true,
          validate: {
            validator: function (v: string) {
              return /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{6,}$/.test(v);
            },
            message: (props) => `${props.value} 는 문자, 숫자를 포함해 6글자 이상이어야 합니다.`,
          },
        },
        name: { type: String, required: true },
      },
      {
        timestamps: true,
        toJSON: {
          transform: function (doc, ret) {
            delete ret.password;
            return ret;
          },
        },
      }
    );
    
    userSchema.pre('save', autoIncrement('userSeq'));
    
    userSchema.pre('save', async function (next) {
      if (!this.isModified('password')) {
        next();
      }
    
      const salt = await bcrypt.genSalt(10);
      this.password = await bcrypt.hash(this.password, salt);
      next();
    });
    
    userSchema.methods.matchPassword = async function (enteredPassword: string) {
      return await bcrypt.compare(enteredPassword, this.password);
    };
    
    const User = mongoose.model<IUser>('User', userSchema);
    
    export default User;

    유저 모듈은 거의 그대로지만 안전장치를 위해 하나의 옵션을 추가했다.

        toJSON: {
          transform: function (doc, ret) {
            delete ret.password;
            return ret;
          },
        },

    바로 스키마의 두 번째 인수인 스키마 옵션 객체에 위 옵션을 추가한 것이다.

     

    타입스크립트의 Object와 몽구스에선 'toJSON' 메서드를 기본적으로 제공한다.

     

    이는 예상할 수 있듯이 해당 객체를 JSON으로 파싱해 반환하는 역할을 한다.

     

    문제는 이를 그대로 사용할 경우, 잘못하다간 비밀번호와 같은 민감한 정보가 그대로 반환될 가능성이 있다는 것이다.

     

    따라서 스키마 옵션 객체 안에서 toJSON 메서드를 오버라이드해서 특정한 동작을 하도록 만들었는데,

     

    바로 transform 메서드 안에서 'ret.password'를 삭제함으로써 User 객체가 어디에서 어떤 식으로

     

    JSON으로 변환되더라도 password 필드를 제외하게 된다.

     

    참고로 여기서 doc이란 원본이 되는 도큐먼트를 말하며, ret란 리턴 값, 그러니까 도큐먼트의 JSON 객체를 가리킨다.

     

    /Feed.ts

     

    계속해서 피드의 모델을 보자.

    import mongoose, { Schema, Document } from 'mongoose';
    import { autoIncrement } from '../middleware/autoIncrement';
    
    interface IFeed extends Document {
      feedSeq: number;
      userSeq: number;
      title: string;
      body: string;
    }
    
    const feedSchema: Schema<IFeed> = new Schema(
      {
        feedSeq: { type: Number, unique: true, required: true, default: 1 },
        userSeq: { type: Number, required: true },
        title: { type: String, required: true },
        body: { type: String, required: true },
      },
      {
        timestamps: true,
      }
    );
    
    feedSchema.pre('save', autoIncrement('feedSeq'));
    
    const Feed = mongoose.model<IFeed>('Feed', feedSchema);
    
    export default Feed;

    이 모듈에선 사용자 정보 등록을 위한 'userSeq'만 추가되었다.

     

    /src/controllers

     

    이번 글부터 본격적인 CRUD가 추가되기 때문에 컨트롤러 모듈의 덩치가 매우 커진다.

     

    변경된 부분과 추가된 부분 위주로 살펴보도록 하자.

     

    /userController.ts

     

    import { Request, Response } from 'express';
    import asyncHandler from 'express-async-handler';
    import User from '../models/User';
    import generateToken from '../utils/generateToken';
    import sendErrorResponse from '../utils/sendErrorResponse';
    
    const registerUser = asyncHandler(async (req: Request, res: Response) => {
      const { id, password, name } = req.body;
    
      const userExists = await User.findOne({ id });
    
      if (userExists) {
        sendErrorResponse(res, 400, '이미 존재하는 ID 입니다.');
        return;
      }
    
      const newUser = new User({ id, password, name });
      const savedUser = await newUser.save();
    
      if (savedUser) {
        res.status(200).json({
          userSeq: savedUser.userSeq,
          id: savedUser.id,
          name: savedUser.name,
          token: generateToken(savedUser.userSeq, savedUser.name),
        });
      } else {
        sendErrorResponse(res, 400, '잘못된 사용자 데이터 입니다.');
        return;
      }
    });
    
    const loginUser = asyncHandler(async (req: Request, res: Response) => {
      const { id, password } = req.body;
    
      const user = await User.findOne({ id });
    
      if (user && (await user.matchPassword(password))) {
        res.status(200).json({
          userSeq: user.userSeq,
          id: user.id,
          name: user.name,
          token: generateToken(user.userSeq, user.name),
        });
      } else {
        sendErrorResponse(res, 401, '이메일 혹은 비밀번호를 잘못 입력하셨습니다.');
        return;
      }
    });
    
    const getUserByUserSeq = asyncHandler(async (req: Request, res: Response) => {
      const userSeq = req.params.userSeq;
      const user = await User.findOne({ userSeq: userSeq });
    
      if (!user) {
        sendErrorResponse(res, 404, `${userSeq} 시퀀스에 해당하는 사용자가 없습니다.`);
        return;
      }
    
      res.status(200).json({
        id: user.id,
        name: user.name,
      });
    });
    
    const updateUser = asyncHandler(async (req: Request, res: Response) => {
      const authUserSeq = res.locals.user.userSeq;
    
      const userSeq = Number(req.params.userSeq);
    
      const user = await User.findOne({ userSeq: userSeq });
    
      if (!user) {
        sendErrorResponse(res, 404, `${userSeq} 시퀀스에 해당하는 사용자가 없습니다.`);
        return;
      }
    
      if (user.userSeq !== authUserSeq) {
        sendErrorResponse(res, 401, 'Unauthorized');
        return;
      }
    
      user.name = req.body.name;
    
      await user.updateOne({ name: req.body.name });
      const updatedUser = await User.findOne({ userSeq: userSeq });
      res.status(200).json({ name: updatedUser!.name });
    });
    
    const updateUserPassword = asyncHandler(async (req: Request, res: Response) => {
      const userSeq = req.params.userSeq;
    
      const { oldPassword, newPassword, newPasswordRepeat } = req.body;
    
      const user = await User.findOne({ userSeq: userSeq });
    
      if (!user) {
        sendErrorResponse(res, 404, `${userSeq} 시퀀스에 해당하는 사용자가 없습니다.`);
        return;
      }
    
      if (user.userSeq !== Number(userSeq)) {
        sendErrorResponse(res, 401, 'Unauthorized');
        return;
      }
    
      if (!(await user.matchPassword(oldPassword))) {
        sendErrorResponse(res, 400, '비밀번호가 틀렸습니다.');
        return;
      }
    
      if (newPassword !== newPasswordRepeat) {
        sendErrorResponse(res, 400, '새 비밀번호를 다시 입력해 주세요.');
        return;
      }
    
      user.password = newPassword;
      await user.save();
      res.status(200).json({ message: '비밀번호 변경 완료' });
    });
    
    const deleteUser = asyncHandler(async (req: Request, res: Response) => {
      const authUserSeq = res.locals.user.userSeq;
    
      const userSeq = Number(req.params.userSeq);
    
      const user = await User.findOne({ userSeq: userSeq });
    
      if (!user) {
        sendErrorResponse(res, 404, `${userSeq} 시퀀스에 해당하는 사용자가 없습니다.`);
        return;
      }
    
      if (user.userSeq !== authUserSeq) {
        sendErrorResponse(res, 401, 'Unauthorized');
        return;
      }
    
      await User.deleteOne({ userSeq: authUserSeq });
      res.status(200).json({ message: '탈퇴 완료' });
    });
    
    export { registerUser, loginUser, getUserByUserSeq, updateUser, updateUserPassword, deleteUser };

    가장 먼저, 기존에 존재하던 에러 핸들링 방식이 전부 위에 구현한 방식으로 통일되었다.

     

    이를 위해 유틸 모듈을 가져왔으며,

    import sendErrorResponse from '../utils/sendErrorResponse';

    기존 코드에 비해 가독성이 좋고 통일감이 생긴 것을 확인할 수 있다.

     

    계속해서 추가된 메서드를 하나씩 살펴보자.

    const getUserByUserSeq = asyncHandler(async (req: Request, res: Response) => {
      const userSeq = req.params.userSeq;
      const user = await User.findOne({ userSeq: userSeq });
    
      if (!user) {
        sendErrorResponse(res, 404, `${userSeq} 시퀀스에 해당하는 사용자가 없습니다.`);
        return;
      }
    
      res.status(200).json({
        id: user.id,
        name: user.name,
      });
    });

    이 코드는 이름대로 userSeq를 이용해 사용자 정보를 가져오는 메서드이다.

     

    여기서

    const userSeq = req.params.userSeq;

    이 코드는 라우트 파라미터, 그러니까 '/users/1'에서 1을 가져오는 역할을 한다.

     

    참고로 이와 비슷하게 생긴

    req.query.userId

    이 코드는 쿼리 파라미터, 그러니까 /users? userId=1에서 userId를 가져오는 역할을 한다.

     

    나머지 로직은 이제 어려울 게 없으니 넘어가고,

     

    요청이 제대로 처리된 경우 200 코드와 함께 사용자의 아이디와 이름만 반환하는 것을 확인할 수 있다.

     

    이처럼 필요 없는 정보를 제외하고 특정 정보만 보내는 것이 서버 부담 차원에서도 좋다.

    const updateUser = asyncHandler(async (req: Request, res: Response) => {
      const authUserSeq = res.locals.user.userSeq;
    
      const userSeq = Number(req.params.userSeq);
    
      const user = await User.findOne({ userSeq: userSeq });
    
      if (!user) {
        sendErrorResponse(res, 404, `${userSeq} 시퀀스에 해당하는 사용자가 없습니다.`);
        return;
      }
    
      if (user.userSeq !== authUserSeq) {
        sendErrorResponse(res, 401, 'Unauthorized');
        return;
      }
    
      user.name = req.body.name;
    
      await user.updateOne({ name: req.body.name });
      const updatedUser = await User.findOne({ userSeq: userSeq });
      res.status(200).json({ name: updatedUser!.name });
    });

    계속해서 사용자 정보(여기서는 이름)를 수정하는 메서드이다.

     

    위에서 정의한 authentication.ts 모듈을 사용해 'locals'에서 원하는 정보를 가져오는 것을 확인할 수 있다.

     

    이후 요청에서의 userSeq를 추출해 두 값을 비교한 뒤 같다면 사용자의 정보를 업데이트하는 것을 볼 수 있다.

     

    여기서 주의할 점은, 여기까지만 코드를 입력하고 실행시키면 사용자 검증 로직에서 에러가 발생한다.

     

    이는 미들웨어 함수의 실행위치를 정해주지 않았기 때문인데, 잠시 후에 라우트 모듈을 수정한 뒤엔 제대로 작동할 것이다.

     

    먼저 테스트해보고 싶다면 아래로 내려서 라우트 모듈을 수정하는 것도 괜찮은 선택이다.

    const updateUserPassword = asyncHandler(async (req: Request, res: Response) => {
      const userSeq = req.params.userSeq;
    
      const { oldPassword, newPassword, newPasswordRepeat } = req.body;
    
      const user = await User.findOne({ userSeq: userSeq });
    
      if (!user) {
        sendErrorResponse(res, 404, `${userSeq} 시퀀스에 해당하는 사용자가 없습니다.`);
        return;
      }
    
      if (user.userSeq !== Number(userSeq)) {
        sendErrorResponse(res, 401, 'Unauthorized');
        return;
      }
    
      if (!(await user.matchPassword(oldPassword))) {
        sendErrorResponse(res, 400, '비밀번호가 틀렸습니다.');
        return;
      }
    
      if (newPassword !== newPasswordRepeat) {
        sendErrorResponse(res, 400, '새 비밀번호를 다시 입력해 주세요.');
        return;
      }
    
      user.password = newPassword;
      await user.save();
      res.status(200).json({ message: '비밀번호 변경 완료' });
    });

    계속해서 비밀번호 수정 메서드이다.

     

    마찬가지로 검증 로직 수행 뒤 필요한 정보를 얻어오고,

     

    이번엔 요청 바디에서 먼저 필요한 변수를 얻어온 뒤 로직을 진행시킨다.

     

    에러 핸들링은 읽어보면 알 수 있을 것이고, 여기서는 기존 비밀번호 한 번, 바꿀 비밀번호를 두 번 입력해서 바꾸도록 만들었다.

    const deleteUser = asyncHandler(async (req: Request, res: Response) => {
      const authUserSeq = res.locals.user.userSeq;
    
      const userSeq = Number(req.params.userSeq);
    
      const user = await User.findOne({ userSeq: userSeq });
    
      if (!user) {
        sendErrorResponse(res, 404, `${userSeq} 시퀀스에 해당하는 사용자가 없습니다.`);
        return;
      }
    
      if (user.userSeq !== authUserSeq) {
        sendErrorResponse(res, 401, 'Unauthorized');
        return;
      }
    
      await User.deleteOne({ userSeq: authUserSeq });
      res.status(200).json({ message: '탈퇴 완료' });
    });

    마지막으로 회원 탈퇴 메서드이다.

     

    이전의 로직과 비교해서 특별히 다른 점은 없다.

    export { registerUser, loginUser, getUserByUserSeq, updateUser, updateUserPassword, deleteUser };

    마무리로 이번에 추가된 메서드들을 내보내며 끝난다.

     

    feedController.ts

     

    import { Request, Response } from 'express';
    import asyncHandler from 'express-async-handler';
    import Feed from '../models/Feed';
    import sendErrorResponse from '../utils/sendErrorResponse';
    
    const createFeed = asyncHandler(async (req: Request, res: Response) => {
      const userSeq = res.locals.user.userSeq;
    
      const newFeed = new Feed({ ...req.body, userSeq: userSeq });
    
      const savedFeed = await newFeed.save();
      res.json(savedFeed);
    });
    
    const getFeedByFeedSeq = asyncHandler(async (req: Request, res: Response) => {
      const feedSeq = req.params.feedSeq;
      const feed = await Feed.findOne({ feedSeq: feedSeq });
    
      if (!feed) {
        sendErrorResponse(res, 404, `${feedSeq} 시퀀스에 해당하는 피드가 없습니다.`);
        return;
      }
    
      res.status(200).json(feed);
    });
    
    const getAllFeeds = asyncHandler(async (req: Request, res: Response) => {
      const feeds = await Feed.find({});
      res.json(feeds);
    });
    
    const updateFeed = asyncHandler(async (req: Request, res: Response) => {
      const userSeq = res.locals.user.userSeq;
    
      const feedSeq = Number(req.params.feedSeq);
      const feed = await Feed.findOne({ feedSeq: feedSeq });
    
      if (!feed) {
        sendErrorResponse(res, 404, `${feedSeq} 시퀀스에 해당하는 피드가 없습니다.`);
        return;
      }
    
      if (feed.userSeq !== userSeq) {
        sendErrorResponse(res, 401, 'Unauthorized');
        return;
      }
    
      feed.title = req.body.title;
      feed.body = req.body.body;
    
      const updatedFeed = await feed.save();
      res.json(updatedFeed);
    });
    
    const deleteFeed = asyncHandler(async (req: Request, res: Response) => {
      const userSeq = res.locals.user.userSeq;
    
      const feedSeq = Number(req.params.feedSeq);
      const feed = await Feed.findOne({ feedSeq: feedSeq });
    
      if (!feed) {
        sendErrorResponse(res, 404, `${feedSeq} 시퀀스에 해당하는 피드가 없습니다.`);
        return;
      }
    
      if (feed.userSeq !== userSeq) {
        sendErrorResponse(res, 401, 'Unauthorized');
        return;
      }
    
      await Feed.deleteOne({ feedSeq: feedSeq });
      res.status(200).json({ message: '삭제 완료' });
    });
    
    export { createFeed, getFeedByFeedSeq, getAllFeeds, updateFeed, deleteFeed };

    여기도 우선 모든 에러 핸들링이 하나로 통일되었다.

     

    또한 추가된 메서드는 없지만, 생성, 수정, 삭제의 경우 생성 시 userSeq를 함께 저장하고,

     

    수정, 삭제 시 해당 userSeq를 기준으로 검증이 끝나면 로직이 진행되도록 만들었다.

     

    특별할 건 없으니 넘어간다.

     

    /src/routes

     

    /userRoutes.ts

     

    import express from 'express';
    import {
      registerUser,
      loginUser,
      getUserByUserSeq,
      updateUser,
      updateUserPassword,
      deleteUser,
    } from '../controllers/userController';
    import { authMiddleware } from '../middleware/authentication';
    
    const router = express.Router();
    
    router.route('/register').post(registerUser);
    router.route('/login').post(loginUser);
    router.route('/:userSeq').get(getUserByUserSeq);
    router.route('/:userSeq/edit').patch(authMiddleware, updateUser);
    router.route('/:userSeq/changePassword').patch(authMiddleware, updateUserPassword);
    router.route('/:userSeq/delete').delete(authMiddleware, deleteUser);
    
    export default router;

    추가된 핸들러 메서드를 가져왔으며, 가장 중요한 변경점은 

    import { authMiddleware } from '../middleware/authentication';

    이렇게 인증 미들웨어를 가져와 필요한 라우트 엔드포인트에 추가한 것이다.

    router.route('/:userSeq/edit').patch(authMiddleware, updateUser);
    router.route('/:userSeq/changePassword').patch(authMiddleware, updateUserPassword);
    router.route('/:userSeq/delete').delete(authMiddleware, deleteUser);

    이렇게 세 군데에 추가해 주었는데, 중요한 것은 미들웨어 함수가 핸들러 메서드보다 앞에 쓰여있어야 한다.

     

    이는 미들웨어 함수의 특성상 쓰인 순서대로 호출되기 때문이다.

     

    추가로 각 메서드의 엔드포인트를 목적에 맞게 수정했다.

     

    /feedRoutes.ts

     

    import express from 'express';
    import { createFeed, getFeedByFeedSeq, getAllFeeds, updateFeed, deleteFeed } from '../controllers/feedController';
    import { authMiddleware } from '../middleware/authentication';
    
    const router = express.Router();
    
    router.post('/', authMiddleware, createFeed);
    router.get('/:feedSeq', getFeedByFeedSeq);
    router.get('/', getAllFeeds);
    router.patch('/:feedSeq/edit', authMiddleware, updateFeed);
    router.delete('/:feedSeq/delete', authMiddleware, deleteFeed);
    
    export default router;

    여기서도 마찬가지이다. 추가된 핸들러 메서드와 인증 미들웨어를 가져와 배치하고, 엔드포인트를 수정했다.

     

    /src/server.ts

     

    import express, { Request, Response, NextFunction } from 'express';
    import dotenv from 'dotenv';
    import connectDB from './config/db';
    import userRoutes from './routes/userRoutes';
    import feedRoutes from './routes/feedRoutes';
    import morgan from 'morgan';
    import sendErrorResponse from './utils/sendErrorResponse';
    
    dotenv.config();
    
    connectDB();
    
    const app = express();
    const port = process.env.PORT || 3000;
    
    app.use(express.json());
    app.use(morgan('dev'));
    
    app.use('/users', userRoutes);
    app.use('/feeds', feedRoutes);
    
    app.get('/', (req, res) => {
      res.send('Hello World!');
    });
    
    // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
    app.use((err: any, req: Request, res: Response, next: NextFunction) => {
      sendErrorResponse(res, err.status || 500, err.message);
      return;
    });
    
    app.listen(port, () => {
      console.log(`Server is running at http://localhost:${port}`);
    });

    마지막으로 서버 파일이다. 에러 핸들링 방식을 통일한 것 외에는 만진 것이 없다.

     


    이렇게 이번 글에서 목표로 했던 구현은 끝이 났다.

     

    추가된 라인은 굉장히 많지만 이전에 충분히 연습을 했기 때문에 그다지 어렵지 않았던 것 같다.

     

    계속해서 다음 글에선 'Comment' 모듈을 추가해서 몽구스에서 1:N 관계를 다루는 법에 대해 공부하고 구현할 계획이다.

     

    어디까지 구현이 진행될지는 모르겠지만, 일단 오늘은 여기서 끝!

    반응형
    댓글
    공지사항
    최근에 올라온 글
    최근에 달린 댓글
    Total
    Today
    Yesterday
    링크
    «   2024/06   »
    1
    2 3 4 5 6 7 8
    9 10 11 12 13 14 15
    16 17 18 19 20 21 22
    23 24 25 26 27 28 29
    30
    글 보관함