diff --git a/3rd_Seminar/node-typescript-init/.gitignore b/3rd_Seminar/node-typescript-init/.gitignore new file mode 100644 index 0000000..a9ba020 --- /dev/null +++ b/3rd_Seminar/node-typescript-init/.gitignore @@ -0,0 +1,146 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/node +# Edit at https://www.toptal.com/developers/gitignore?templates=node + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* +yarn.lock + +### Node Patch ### +# Serverless Webpack directories +.webpack/ + +# Optional stylelint cache + +# SvelteKit build / generate output +.svelte-kit + +# End of https://www.toptal.com/developers/gitignore/api/node \ No newline at end of file diff --git a/3rd_Seminar/node-typescript-init/README.md b/3rd_Seminar/node-typescript-init/README.md new file mode 100644 index 0000000..cac9ad2 --- /dev/null +++ b/3rd_Seminar/node-typescript-init/README.md @@ -0,0 +1,2 @@ +# node-typescript-init +Node.js, Typescript, MongoDB (mongoose) Initial Boiler Plate diff --git a/3rd_Seminar/node-typescript-init/nodemon.json b/3rd_Seminar/node-typescript-init/nodemon.json new file mode 100644 index 0000000..7e70d98 --- /dev/null +++ b/3rd_Seminar/node-typescript-init/nodemon.json @@ -0,0 +1,11 @@ +{ + "watch": [ + "src", + ".env" + ], + "ext": "js,ts,json", + "ignore": [ + "src/**/*.spec.ts" + ], + "exec": "ts-node --transpile-only ./src/index.ts" +} \ No newline at end of file diff --git a/3rd_Seminar/node-typescript-init/package.json b/3rd_Seminar/node-typescript-init/package.json new file mode 100644 index 0000000..d132071 --- /dev/null +++ b/3rd_Seminar/node-typescript-init/package.json @@ -0,0 +1,26 @@ +{ + "name": "node-typescript-init", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "dev": "nodemon", + "build": "tsc && node dist" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@types/express": "^4.17.13", + "@types/mongoose": "^5.11.97", + "@types/node": "^17.0.25", + "nodemon": "^2.0.15", + "ts-node": "^10.7.0", + "typescript": "^4.6.3" + }, + "dependencies": { + "dotenv": "^16.0.0", + "express": "^4.17.3", + "express-validator": "^6.14.0", + "mongoose": "^6.3.1" + } +} diff --git a/3rd_Seminar/node-typescript-init/src/config/index.ts b/3rd_Seminar/node-typescript-init/src/config/index.ts new file mode 100644 index 0000000..4449be4 --- /dev/null +++ b/3rd_Seminar/node-typescript-init/src/config/index.ts @@ -0,0 +1,23 @@ +import dotenv from "dotenv"; + +// Set the NODE_ENV to 'development' by default +process.env.NODE_ENV = process.env.NODE_ENV || "development"; + +const envFound = dotenv.config(); +if (envFound.error) { + // This error should crash whole process + + throw new Error("⚠️ Couldn't find .env file ⚠️"); +} + +export default { + /** + * Your favorite port + */ + port: parseInt(process.env.PORT as string, 10) as number, + + /** + * MongoDB URI + */ + mongoURI: process.env.MONGODB_URI as string, +}; \ No newline at end of file diff --git a/3rd_Seminar/node-typescript-init/src/controllers/UserController.ts b/3rd_Seminar/node-typescript-init/src/controllers/UserController.ts new file mode 100644 index 0000000..514e9ff --- /dev/null +++ b/3rd_Seminar/node-typescript-init/src/controllers/UserController.ts @@ -0,0 +1,97 @@ +import express, { Request, Response } from "express"; +import { UserCreateDto } from "../interfaces/user/UserCreateDto"; +import statusCode from "../modules/statusCode"; +import message from "../modules/responseMessage"; +import util from "../modules/util"; +import { UserService } from "../services"; +import { UserUpdateDto } from "../interfaces/user/UserUpdateDto"; + +/** + * @route GET /user/:userId + * @desc Get User + * @access Public + * @param req + * @param res + */ +const findUserById = async (req: Request, res: Response) => { + const { userId } = req.params; + + try { + const user = await UserService.findUserById(userId); + + if (!user) { + return res.status(statusCode.NOT_FOUND).send(util.fail(statusCode.NOT_FOUND, message.NOT_FOUND)); + } + + res.status(statusCode.OK).send(util.success(statusCode.OK, message.READ_USER_SUCCESS, user)); + } catch (error) { + res.status(statusCode.INTERNAL_SERVER_ERROR).json(util.fail(statusCode.INTERNAL_SERVER_ERROR, message.INTERNAL_SERVER_ERROR)); + } +} + + +/** + * @route POST /user + * @desc Create User + * @access Public + * @param req + * @param res + */ +const createUser = async (req: Request, res: Response) => { + const userCreateDto: UserCreateDto = req.body; // User Create Dto로 req.body 값을 받아옴 + + try { + const data = await UserService.createUser(userCreateDto); + + res.status(statusCode.CREATED).send(util.success(statusCode.CREATED, message.CREATE_USER_SUCCESS, data)); + + } catch (error) { + console.log(error); + // 서버 내부에서 오류 발생 + res.status(statusCode.INTERNAL_SERVER_ERROR).json(util.fail(statusCode.INTERNAL_SERVER_ERROR, message.INTERNAL_SERVER_ERROR)); + } +} + +/** + * @route PUT /user/:userId + * @desc Update User + * @access Public + */ +const updateUser = async (req: Request, res: Response) => { + const userUpdateDto: UserUpdateDto = req.body; + const { userId } = req.params; + + try { + await UserService.updateUser(userId, userUpdateDto); + + res.status(statusCode.NO_CONTENT).send(); + } catch (error) { + res.status(statusCode.INTERNAL_SERVER_ERROR).json(util.fail(statusCode.INTERNAL_SERVER_ERROR, message.INTERNAL_SERVER_ERROR)); + } +} + +/** + * @route DELETE /user/:userId + * @desc Delete User + * @access Public + */ +const deleteUser = async (req: Request, res: Response) => { + const { userId } = req.params; + + try { + await UserService.deleteUser(userId); + + res.status(statusCode.NO_CONTENT).send(); + + } catch (error) { + res.status(statusCode.INTERNAL_SERVER_ERROR).json(util.fail(statusCode.INTERNAL_SERVER_ERROR, message.INTERNAL_SERVER_ERROR)); + } + +} + +export default { + findUserById, + createUser, + updateUser, + deleteUser +} \ No newline at end of file diff --git a/3rd_Seminar/node-typescript-init/src/controllers/index.ts b/3rd_Seminar/node-typescript-init/src/controllers/index.ts new file mode 100644 index 0000000..fe401f1 --- /dev/null +++ b/3rd_Seminar/node-typescript-init/src/controllers/index.ts @@ -0,0 +1,6 @@ +import UserController from "./UserController" + +// controller index file +export { + UserController +} \ No newline at end of file diff --git a/3rd_Seminar/node-typescript-init/src/index.ts b/3rd_Seminar/node-typescript-init/src/index.ts new file mode 100644 index 0000000..2bbc4e6 --- /dev/null +++ b/3rd_Seminar/node-typescript-init/src/index.ts @@ -0,0 +1,42 @@ +import express, { Request, Response, NextFunction } from "express"; +import config from "./config"; +const app = express(); +import connectDB from "./loaders/db"; +import routes from './routes'; +require('dotenv').config(); + +connectDB(); + +app.use(express.urlencoded({ extended: true })); +app.use(express.json()); + +app.use(routes); //라우터 +// error handler + +interface ErrorType { + message: string; + status: number; +} + +app.use(function (err: ErrorType, req: Request, res: Response, next: NextFunction) { + + 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(config.port, () => { + console.log(` + ################################################ + 🛡️ Server listening on port 🛡️ + ################################################ + `); + }) + .on("error", (err) => { + console.error(err); + process.exit(1); + }); \ No newline at end of file diff --git a/3rd_Seminar/node-typescript-init/src/interfaces/common/PoseBaseResponseDto.ts b/3rd_Seminar/node-typescript-init/src/interfaces/common/PoseBaseResponseDto.ts new file mode 100644 index 0000000..6af2f99 --- /dev/null +++ b/3rd_Seminar/node-typescript-init/src/interfaces/common/PoseBaseResponseDto.ts @@ -0,0 +1,5 @@ +import mongoose from "mongoose"; + +export interface PostBaseResponseDto { + _id: mongoose.Schema.Types.ObjectId; +} \ No newline at end of file diff --git a/3rd_Seminar/node-typescript-init/src/interfaces/school/SchoolInfo.ts b/3rd_Seminar/node-typescript-init/src/interfaces/school/SchoolInfo.ts new file mode 100644 index 0000000..dff9d13 --- /dev/null +++ b/3rd_Seminar/node-typescript-init/src/interfaces/school/SchoolInfo.ts @@ -0,0 +1,4 @@ +export interface SchoolInfo { + name: string; + major: string; +} \ No newline at end of file diff --git a/3rd_Seminar/node-typescript-init/src/interfaces/user/UserCreateDto.ts b/3rd_Seminar/node-typescript-init/src/interfaces/user/UserCreateDto.ts new file mode 100644 index 0000000..4ac313b --- /dev/null +++ b/3rd_Seminar/node-typescript-init/src/interfaces/user/UserCreateDto.ts @@ -0,0 +1,9 @@ +import { SchoolInfo } from "../school/SchoolInfo"; + +export interface UserCreateDto { + name: string; + phone: string; + email: string; + age?: number; + school?: SchoolInfo; +} \ No newline at end of file diff --git a/3rd_Seminar/node-typescript-init/src/interfaces/user/UserInfo.ts b/3rd_Seminar/node-typescript-init/src/interfaces/user/UserInfo.ts new file mode 100644 index 0000000..853cac4 --- /dev/null +++ b/3rd_Seminar/node-typescript-init/src/interfaces/user/UserInfo.ts @@ -0,0 +1,9 @@ +import { SchoolInfo } from "../school/SchoolInfo"; + +export interface UserInfo { + name: string; + phone: string; + email: string; + age: number; + school: SchoolInfo; +} \ No newline at end of file diff --git a/3rd_Seminar/node-typescript-init/src/interfaces/user/UserResponseDto.ts b/3rd_Seminar/node-typescript-init/src/interfaces/user/UserResponseDto.ts new file mode 100644 index 0000000..587ba01 --- /dev/null +++ b/3rd_Seminar/node-typescript-init/src/interfaces/user/UserResponseDto.ts @@ -0,0 +1,6 @@ +import mongoose from "mongoose"; +import { UserCreateDto } from "./UserCreateDto"; + +export interface UserResponseDto extends UserCreateDto { + _id: mongoose.Schema.Types.ObjectId; +} \ No newline at end of file diff --git a/3rd_Seminar/node-typescript-init/src/interfaces/user/UserUpdateDto.ts b/3rd_Seminar/node-typescript-init/src/interfaces/user/UserUpdateDto.ts new file mode 100644 index 0000000..fcdf124 --- /dev/null +++ b/3rd_Seminar/node-typescript-init/src/interfaces/user/UserUpdateDto.ts @@ -0,0 +1,9 @@ +import { SchoolInfo } from "../school/SchoolInfo"; + +export interface UserUpdateDto { + name?: string; + phone?: string; + email?: string; + age?: number; + school?: SchoolInfo; +} \ No newline at end of file diff --git a/3rd_Seminar/node-typescript-init/src/loaders/db.ts b/3rd_Seminar/node-typescript-init/src/loaders/db.ts new file mode 100644 index 0000000..4971bfc --- /dev/null +++ b/3rd_Seminar/node-typescript-init/src/loaders/db.ts @@ -0,0 +1,17 @@ +import mongoose from "mongoose"; +import config from "../config"; + +const connectDB = async () => { + try { + await mongoose.connect(config.mongoURI); + + mongoose.set('autoCreate', true); + + console.log("Mongoose Connected ..."); + } catch (err: any) { + console.error(err.message); + process.exit(1); + } +}; + +export default connectDB; diff --git a/3rd_Seminar/node-typescript-init/src/models/User.ts b/3rd_Seminar/node-typescript-init/src/models/User.ts new file mode 100644 index 0000000..f1c07e7 --- /dev/null +++ b/3rd_Seminar/node-typescript-init/src/models/User.ts @@ -0,0 +1,27 @@ +import mongoose from "mongoose"; +import { UserInfo } from "../interfaces/user/UserInfo"; + +const UserSchema = new mongoose.Schema({ + name: { + type: String, + require: true + }, + phone: { + type: String, + required: true + }, + email: { + type: String, + required: true, + unique: true + }, + age: { + type: Number, + }, + school: { + name: { type: String }, + major: { type: String } + } +}); + +export default mongoose.model("User", UserSchema); \ No newline at end of file diff --git a/3rd_Seminar/node-typescript-init/src/modules/responseMessage.ts b/3rd_Seminar/node-typescript-init/src/modules/responseMessage.ts new file mode 100644 index 0000000..87440b7 --- /dev/null +++ b/3rd_Seminar/node-typescript-init/src/modules/responseMessage.ts @@ -0,0 +1,13 @@ +const message = { + NULL_VALUE: '필요한 값이 없습니다.', + NOT_FOUND: '존재하지 않는 자원', + BAD_REQUEST: '잘못된 요청', + INTERNAL_SERVER_ERROR: '서버 내부 오류', + + READ_USER_SUCCESS: '유저 조회 성공', + CREATE_USER_SUCCESS: '유저 생성 성공', + DELETE_USER_SUCCESS: '유저 삭제 성공', + UPDATE_USER_SUCCESS: '유저 수정 성공' +} + +export default message; \ No newline at end of file diff --git a/3rd_Seminar/node-typescript-init/src/modules/statusCode.ts b/3rd_Seminar/node-typescript-init/src/modules/statusCode.ts new file mode 100644 index 0000000..a8a29eb --- /dev/null +++ b/3rd_Seminar/node-typescript-init/src/modules/statusCode.ts @@ -0,0 +1,15 @@ +const statusCode = { + OK: 200, + CREATED: 201, + NO_CONTENT: 204, + BAD_REQUEST: 400, + UNAUTHORIZED: 401, + FORBIDDEN: 403, + NOT_FOUND: 404, + CONFLICT: 409, + INTERNAL_SERVER_ERROR: 500, + SERVICE_UNAVAILABLE: 503, + DB_ERROR: 600, +}; + +export default statusCode; \ No newline at end of file diff --git a/3rd_Seminar/node-typescript-init/src/modules/util.ts b/3rd_Seminar/node-typescript-init/src/modules/util.ts new file mode 100644 index 0000000..406cc16 --- /dev/null +++ b/3rd_Seminar/node-typescript-init/src/modules/util.ts @@ -0,0 +1,19 @@ +const util = { + success: (status: number, message: string, data?: any) => { + return { + status, + success: true, + message, + data, + }; + }, + fail: (status: number, message: string, data?: any) => { + return { + status, + success: false, + message, + }; + }, +}; + +export default util; \ No newline at end of file diff --git a/3rd_Seminar/node-typescript-init/src/routes/UserRouter.ts b/3rd_Seminar/node-typescript-init/src/routes/UserRouter.ts new file mode 100644 index 0000000..6f1ea53 --- /dev/null +++ b/3rd_Seminar/node-typescript-init/src/routes/UserRouter.ts @@ -0,0 +1,9 @@ +import { Router } from "express"; +import { UserController } from "../controllers/" +const router: Router = Router(); + +router.get('/:userId', UserController.findUserById); +router.post('/', UserController.createUser); +router.put('/:userId', UserController.updateUser); +router.delete('/:userId', UserController.deleteUser); +export default router; \ No newline at end of file diff --git a/3rd_Seminar/node-typescript-init/src/routes/index.ts b/3rd_Seminar/node-typescript-init/src/routes/index.ts new file mode 100644 index 0000000..cdb7fd1 --- /dev/null +++ b/3rd_Seminar/node-typescript-init/src/routes/index.ts @@ -0,0 +1,9 @@ +//router index file +import { Router } from 'express'; +import UserRouter from './UserRouter'; + +const router: Router = Router(); + +router.use('/user', UserRouter); + +export default router; \ No newline at end of file diff --git a/3rd_Seminar/node-typescript-init/src/services/UserService.ts b/3rd_Seminar/node-typescript-init/src/services/UserService.ts new file mode 100644 index 0000000..4694b86 --- /dev/null +++ b/3rd_Seminar/node-typescript-init/src/services/UserService.ts @@ -0,0 +1,69 @@ +import { PostBaseResponseDto } from "../interfaces/common/PoseBaseResponseDto"; +import { UserCreateDto } from "../interfaces/user/UserCreateDto"; +import { UserResponseDto } from "../interfaces/user/UserResponseDto"; +import { UserUpdateDto } from "../interfaces/user/UserUpdateDto"; +import User from "../models/User"; + +const findUserById = async (userId: string): Promise => { + try { + const user = await User.findById(userId); + + if (!user) { + return null; + } + + return user; + } catch (error) { + console.log(error); + throw error; + } +} + +const createUser = async (userCreateDto: UserCreateDto): Promise => { + try { + const user = new User({ + name: userCreateDto.name, + phone: userCreateDto.phone, + email: userCreateDto.email, + age: userCreateDto.age, + school: userCreateDto.school + }); + + await user.save(); + + const data = { + _id: user.id + }; + + return data; + } catch (error) { + console.log(error); + throw error; + } +} + +const updateUser = async (userId: string, userUpdateDto: UserUpdateDto) => { + try { + await User.findByIdAndUpdate(userId, userUpdateDto); + + } catch (error) { + console.log(error); + throw error; + } +} + +const deleteUser = async (userId: string) => { + try { + await User.findByIdAndDelete(userId); + } catch (error) { + console.log(error); + throw error; + } +} + +export default { + findUserById, + createUser, + updateUser, + deleteUser +} \ No newline at end of file diff --git a/3rd_Seminar/node-typescript-init/src/services/index.ts b/3rd_Seminar/node-typescript-init/src/services/index.ts new file mode 100644 index 0000000..0fa9cc2 --- /dev/null +++ b/3rd_Seminar/node-typescript-init/src/services/index.ts @@ -0,0 +1,6 @@ +import UserService from "./UserService" + +//service index file +export { + UserService +} \ No newline at end of file diff --git a/3rd_Seminar/node-typescript-init/tsconfig.json b/3rd_Seminar/node-typescript-init/tsconfig.json new file mode 100644 index 0000000..0f8d5f5 --- /dev/null +++ b/3rd_Seminar/node-typescript-init/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "es6", // 어떤 버전으로 컴파일 + "allowSyntheticDefaultImports": true, // default export가 없는 모듈에서 default imports를 허용 + "experimentalDecorators": true, // decorator 실험적 허용 + "emitDecoratorMetadata": true, // 데코레이터가 있는 선언에 대해 특정 타입의 메타 데이터를 내보내는 실험적인 지원 + "skipLibCheck": true, // 정의 파일 타입 체크 여부 + "moduleResolution": "node", // commonJS -> node 에서 동작 + "module": "commonjs", // import 문법 + "strict": true, // 타입 검사 엄격하게 + "pretty": true, // error 메시지 예쁘게 + "sourceMap": true, // 소스맵 파일 생성 -> .ts가 .js 파일로 트랜스 시 .js.map 생성 + "outDir": "./dist", // 트랜스 파일 (.js) 저장 경로 + "allowJs": true, // js 파일 ts에서 import 허용 + "esModuleInterop": true, // ES6 모듈 사양을 준수하여 CommonJS 모듈을 가져올 수 있게 허용 + "typeRoots": [ + "./src/types/express.d.ts", // 타입(*.d.ts)파일을 가져올 디렉토리 설정 + "./node_modules/@types" // 설정 안할시 기본적으로 ./node_modules/@types + ] + }, + "include": [ + "./src/**/*" // build 시 포함 + ], + "exclude": [ + "node_modules", // build 시 제외 + "tests" + ] +} \ No newline at end of file