개발/TypeScript
🫧TS:: NodeJS + Express로 서버 만들기
hyuunii
2023. 1. 13. 15:22
TS?
아주 아주 간단히 말하자면 JS라는 언어에 에디터 부가 기능을 추가한 느낌이랄까. JS의 상위 호환 버젼이라고 보면 된다.
TS의 가장 큰 특징은 JS를 기본으로 거기에 변수 만들때 타입 지정해줄 수 있다는 것이다. 또한 class, interface 등을 추가해서 자바나 C#과 같은 OOP 프로그래밍을 할 수 있다.
TS로 작성한 코드는 최종적으로 JS로 컴파일되어 실행되는데, 타입을 지정해주는 TS의 특성상 JS와는 다르게 타입 에러 등의 에러들이 코드 실행 '전'에 잡힌다. 이런 장점으로 JS의 전체 에러 15%가량이 TS를 사용함으로써 해결될 수 있다고 한다.
서버 만들기(회원가입 및 로그인까지~!)
1. 초기 설정
npm i
//Node.js 상에서 TypeScript Compiler를 통하지 않고도, 직접 TypeScript를 실행시킬 수 있게 함.
npm i --save-dev ts-node typescript
//Exress 설치
npm i express
//Express 모듈에 대한 type을 지원
npm i @types/express --dev
//Node.js 타입을 추가
npm i @types/node --dev
//tsconfig.json 파일 생성
tsc --init
//필요한 라이브러리 설치
npm i ts-dotenv @types/bcrypt @types/jsonwebtoken ts-mongoose
npm i swagger-cli @types/swagger-ui-express swagger-autogen
2. db 연결 파일(schema/index.ts) 만들기
import "dotenv/config";
import mongoose from "mongoose";
mongoose.set('strictQuery', true);
const DB_HOST: string = process.env.DB_HOST as string;
const connect = () => {
mongoose
.connect(DB_HOST)
.then(() => console.log("mongoDB Connected"))
.catch(err => console.log(err));
};
mongoose.connection.on("error", err => {
console.error("mongoDB connecting error", err);
});
module.exports = connect;
3. 서버 파일(app.ts) 만들기(+db-여기선 mongoDB- 연결)
🌟그냥 JS에서 하던대로 const로 express같은거 선언하는 것 아니고 import로 불러와야한다~!
import "dotenv/config";
import helmet from "helmet";
import express, {Express} from "express";
const app: Express = express();
import http from "http";
const server = http.createServer(app)
import routes from "./routes";
import mongoose from "mongoose";
const connect = require("./schema");
connect();
app.use(express.json());
app.use("/", routes);
const port = process.env.PORT || 3000;
server.listen(port, () => {
console.log(`${port}번 포트로 열렸습니다.`);
});
4. 라우팅 분리
import { Router } from "express";
const router = Router();
import UsersController from "../controllers/users";
const usersController = new UsersController;
import { auth_middleware } from "../middleware/auth-middleware";
//회원가입
router.post("/signup", usersController.signUp);
//유저 id 중복 검사
router.post("/Dup/Id", usersController.findDupId)
//유저 nickName 중복 검사
router.post("/Dup/Nick", usersController.findDupNick);
// 로그인
router.post("/login", usersController.login);
export default router
5. routes/users.router.ts
import { Router } from "express";
const router = Router();
import UsersController from "../controllers/users";
const usersController = new UsersController;
import { auth_middleware } from "../middleware/auth-middleware";
//회원가입
router.post("/signup", usersController.signUp);
// 로그인
router.post("/login", usersController.login);
export default router;
6. controllers/users.ts
import "dotenv/config";
import { NextFunction, Request, Response } from "express";
import usersService from "../services/users";
import jwt from "jsonwebtoken";
class usersController {
public usersService = new usersService();
//회원가입
public signUp = async (req: Request, res: Response, next: NextFunction) => {
try {
const {userId, nickName, email, password, confirm}
: {userId: string, nickName: string, email: string, password: string, confirm: string}
= req.body;
await this.usersService.signUp({userId, nickName, email, password, confirm});
res.status(201).json({ok: true, statusCode: 201, message: "회원가입 성공!!"});
} catch (err: any) {
res.status(err.status || 400).json({ok: 0, statusCode: err.status, err: err.message})
};
};
//유저 id 중복 검사
public findDupId = async (req: Request, res: Response, next: NextFunction) => {
const {userId}: {userId: string} = req.body;
try {
const findDupIdData = await this.usersService.findDupId(userId);
res.status(201).json({findDupIdData});
} catch (err: any) {
res.status(400).json({message: err.message, statusCode: err.status});
};
};
//유저 nickname 중복 검사
public findDupNick = async (req: Request, res: Response, next: NextFunction) => {
const {nickName}: {nickName: string} = req.body;
try {
const findDupNickData = await this.usersService.findDupNick(nickName);
res.status(201).json({findDupNickData});
} catch (err: any) {
res.status(400).json({message: err.message, statusCode: err.status});
};
};
//로그인
public login = async (req: Request, res: Response, next: NextFunction) => {
try {
const {userId, password}: {userId: string, password: string}= req.body;
//유효성 검사
const login = await this.usersService.login(userId, password);
if (login === null) {
return res.status(404).send({ok: 0, statusCode: 404, errorMessage: "가입 정보를 찾을 수 없습니다."});
};
const getNickName = await this.usersService.getNickName(userId);
const accessToken = await this.usersService.getAccessToken(userId);
const refreshToken = await this.usersService.getRefreshToken();
//refreshToken DB에 업뎃
await this.usersService.updateRefreshToken(userId, refreshToken);
res.status(201).json({
accessToken: `Bearer ${accessToken}}`,
refresh_token: `Bearer ${refreshToken}`,
nickName: {getNickName}&&nickName //🔥
});
} catch (err: any) {
res.status(err.status || 400).json({
ok: 0,
statusCode: err.status,
message: err.message || "로그인 실패.."});
};
};
};
export default usersController;
7. services/users.ts
import "dotenv/config";
import { Router } from 'express';
const router = Router();
import { IUserInputDTO, userUniqueSearchInput } from "../interfaces/IUser";
import { Error } from "../interfaces/Error";
import UsersRepository from "../repositories/users";
// import PostsRepository from "../repositories/posts";
// import CommentsRepository from "../repositories/comments";
import jwt from "jsonwebtoken";
import bcrypt from "bcrypt";
const CHECK_ID = /^[a-zA-Z0-9]{4, 20}$/; // 4 ~ 15으로 변경
const CHECK_PASSWORD = /^[a-zA-Z0-9]{4, 30}$/; // 8~15 으로 변경
const DB_HOST: string = process.env.DB_HOST as string;
const DB_SECRET_KEY: string = process.env.DB_SECRET_KEY as string;
class UserService {
usersRepository = new UsersRepository();
// postsRepository = new PostsRepository();
// commentsRepository = new CommentsRepository();
//회원가입
signUp = async (
{userId, nickName, email, password, confirm}
: {userId: string, nickName: string, email: string, password: string, confirm?: string}
) => {
const isSameId = await this.usersRepository.findUserAccountId(userId);
const isSameNickname = await this.usersRepository.findUserAccountNick(nickName);
//유저 id 중복 검사
if (isSameId) {
const err: Error = new Error(`UserService Error`);
err.status = 409;
err.message = "이미 가입된 아이디가 존재합니다.";
throw err;
};
//유저 nickname 중복 검사
if (isSameNickname) {
const err: Error = new Error(`UserService Error`);
err.status = 409;
err.message = "이미 가입된 닉네임이 존재합니다.";
throw err;
};
//아이디가 최소 9자리가 아닐 경우
if (!CHECK_ID.test(userId)) {
const err: Error = new Error(`UserService Error`);
err.status = 403;
err.message = "아이디는 최소 4자리 이상으로 해주세요.";
throw err;
};
//비밀번호 최소치가 맞지 않을 경우
if (!CHECK_PASSWORD.test(password)) {
const err: Error = new Error(`UserService Error`);
err.status = 403;
err.message = "비밀번호는 최소 4자리 이상으로 해주세요.";
throw err;
};
//비밀번호와 비밀번호 확인이 맞지 않을 경우
if (password !== confirm) {
const err: Error = new Error(`UserService Error`);
err.status = 403;
err.message = "비밀번호와 확인 비밀번호가 일치하지 않습니다.";
throw err;
};
const salt = await bcrypt.genSalt(11);
//반복 횟수를 늘려보자
password = await bcrypt.hash(password, salt);
//자 이제 진짜 가입
const createAccountData = await this.usersRepository.signUp({
userId, nickName, email, password
});
return createAccountData;
};
//유저 id 중복 검사
findDupId = async(userId: string) => {
const findDupId = await this.usersRepository.findUserAccountId(userId);
if (findDupId) {
const err:Error = new Error(`UserService Error`);
err.status = 409;
err.message = "이미 가입된 아이디가 존재합니다.";
throw err;
} else{
return "사용 가능한 아이디입니다."
};
};
//유저 nickname 중복 검사
findDupNick = async(nickName: string) => {
const findDupNick = await this.usersRepository.findUserAccountNick(nickName);
if (findDupNick) {
const err:Error = new Error(`UserService Error`);
err.status = 409;
err.message = "이미 가입된 닉네임이 존재합니다.";
throw err;
} else{
return "사용 가능한 닉네임입니다."
};
};
//로그인
login = async (userId: string, password: string) => {
//회원 여부 체크
const loginData = await this.usersRepository.login(userId);
if (!loginData) {
const err: Error = new Error(`UserService Error`)
err.status = 403;
err.message = "아이디를 확인해주세요.";
throw err;
};
const checkPW = await bcrypt.compare(password, {loginData}&&password); //🔥
if (!checkPW) {
const err: Error = new Error(`UserService Error`)
err.status = 403;
err.message = "패스워드를 확인해주세요.";
throw err;
};
//회원 맞으면 로그인 정보 반환
return { loginData };
};
//nickName 불러오기 by userId
getNickName = async (userId: string) => {
const getNickNameData = await this.usersRepository.findUserAccount(userId);
return getNickNameData;
};
//accessToken 생성
getAccessToken = async (userId: string) => {
const accessToken = jwt.sign({userId}, DB_HOST, {expiresIn: "5m"});
return accessToken;
};
//refreshToken 생성
getRefreshToken = async () => {
const refreshToken = jwt.sign({}, DB_SECRET_KEY, {expiresIn: "1d"});
return refreshToken;
};
//refreshToken DB에 업뎃
updateRefreshToken = async (userId: string, refreshToken: string) => {
console.log(refreshToken);
await this.usersRepository.updateRefreshToken(userId, refreshToken);
const findUserAccountData = await this.usersRepository.findUserAccount(userId);
return findUserAccountData;
};
}
export default UserService;
8. repositories/users.ts
import Users from "../schema/users"
//import Posts from "../schema/posts"
//import Comments from "../schema/comments"
//import Bookmark from "../schema/bookmark"
import { IUserInputDTO, userUniqueSearchInput } from "../interfaces/IUser";
import moment from "moment"
const date = moment().format('YYYY-MM-DD HH:mm:ss');
class usersRepository {
//회원가입
signUp = async (
{userId, nickName, email, password}
: {userId: string, nickName: string, email: string, password: string}
) => {
// create로 회원가입
const createAccountData = await Users.create({
userId, nickName, email, password, visible:"V", tutorial:false, createdAt: date,updatedAt: date
});
//await bookmark.create({ nickName });
return createAccountData;
};
//유저 id 찾기
findUserAccountId = async(userId: string) => {
const findUserAccountData = await Users.findOne({userId});
return findUserAccountData;
} ;
//유저 nickname 찾기
findUserAccountNick = async(nickName: string) => {
const findUserAccountData = await Users.findOne({nickName});
return findUserAccountData;
};
//로그인
login = async(userId: string)=> {
const loginData = await Users.findOne({ userId: userId});
return loginData;
};
//refreshToken 업뎃
updateRefreshToken = async(userId: string, refreshToken: string) => {
const updateRefreshTokenData = await Users.updateOne(
{userId}, {$set: {refresh_token: refreshToken}}
);
return updateRefreshTokenData;
};
//유저 정보 조회
findUserAccount = async(userId: string) => {
const findUserAccountData = await Users.findOne({userId});
return findUserAccountData;
};
};
export default usersRepository;