타입 스크립트로 작성된 회원가입 로직의 흐름을 상세하게 이해하기 위해 작성한 글
import express, { Request, Response } from 'express';
import dotenv from 'dotenv';
import connectDB from './config/db';
import userRoutes from './routes/userRoutes';
dotenv.config();
const app = express();
const PORT = process.env.PORT || 5000;
// Middleware
app.use(express.json());
// Connect to MongoDB
const initializeServer = async () => {
try {
await connectDB();
console.log('Database connected. Starting server');
app.get('/', (req: Request, res: Response) => {
res.send('Hello, TypeScript with Express!');
});
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
} catch (error) {
console.error('Failed to start server : ', error);
}
};
initializeServer();
app.use('/api/users', userRoutes);
JSON 형식으로 파싱할 수 있게 설정합니다.req.body로 제공합니다.undefined가 됩니다.비동기로 데이터베이스 연결을 시도합니다.
서버를 실행하고, 실패 시 에러를 출력합니다.타입: async 함수는 항상 Promise를 반환합니다.
성공 시: Promise (별도의 값을 반환하지 않음).
실패 시: 에러를 던집니다.
Promise의 resolve 값으로 감싸서 반환합니다.Promise가 reject 상태가 됩니다.내부 함수:
app.listen(PORT, ...)app.get('/', ...)const mongoose = require('mongoose');
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI || '', {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('MongoDB connected successfully');
} catch (error) {
console.error('MongoDB connection failed:', error);
process.exit(1);
}
};
export default connectDB;
MongoDB 데이터베이스와 연결합니다.
MONGO_URI는 데이터베이스의 주소를 환경 변수로 지정한 값입니다.
useNewUrlParser: 새로운 URL 구문 해석 방식을 사용합니다.
MongoDB 드라이버 3.6 이전에는 연결 문자열의 해석 로직이 제한적이었습니다
특수 문자 문제:
:가 포함된 경우 mongodb://user:pass:word@localhost:27017를 제대로 인식하지 못함.useNewUrlParser:true
useUnifiedTopology: 더 안정적이고 최신 연결 관리 방식을 사용합니다.
import express from 'express';
import { register } from '../controllers/userController';
import { userValidationRules, validate } from '../validators/userValidator';
const router = express.Router();
router.post('/register', [...userValidationRules, validate], register);
export default router;
독립적으로 분리하여 관리할 수 있게 해줍니다.한 곳에 모아 관리할 수 있습니다.userValidationRulesvalidateimport { body, validationResult } from 'express-validator';
import { Request, Response, NextFunction } from 'express';
export const userValidationRules = [
body('username').notEmpty().withMessage('사용자 이름은 필수입니다.'),
body('email').isEmail().withMessage('유효한 이메일을 입력해주세요.'),
body('password').isLength({ min: 6 }).withMessage('비밀번호는 최소 6자 이상이어야 합니다.'),
];
export const validate = (req: Request, res: Response, next: NextFunction): void => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(400).json({ errors: errors.array() });
} else {
next();
}
};
import { Request, Response, NextFunction } from 'express';
import { registerUser } from '../services/userService';
export const register = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
const { username, email, password } = req.body;
await registerUser(username, email, password);
res.status(201).json({ message: '회원가입이 완료되었습니다.' });
} catch (error) {
next(error);
}
};
import bcrypt from 'bcrypt';
import { createUser, findUserByEmail } from '../repositories/userRepository';
import { BadRequestError } from '../errors/httpError';
export const registerUser = async (username: string, email: string, password: string): Promise<void> => {
const existingUser = await findUserByEmail(email);
if (existingUser) {
throw new BadRequestError('이미 사용 중인 이메일입니다.');
}
const hashedPassword = await bcrypt.hash(password, 10);
await createUser({ username, email, password: hashedPassword });
};
import { User, IUser } from '../models/user';
export const createUser = async (userData: Partial<IUser>): Promise<IUser> => {
const user = new User(userData);
return await user.save();
};
export const findUserByEmail = async (email: string): Promise<IUser | null> => {
return await User.findOne({ email });
};
createUser
findUserByEmail
null을 반환합니다.