5, 6์ฃผ์ฐจ ๋ฏธ์ ์ ํค์ค์คํฌ ํํ์ ์ฑ์ ๊ฐ๋ฐํ๋ ๊ฒ์ ๋๋ค. ํ๋ก ํธ์๋๋ ๋ฆฌ์กํธ, ๋ฐฑ์๋๋ NestJS๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๊ฐ๋ฐํฉ๋๋ค.
์ด๋ฒ ๋ฏธ์ ์์ ํ์ ์คํฌ๋ฆฝํธ๋ฅผ ์ด์ฉํ๋๋ฐ, ์ค์ ์ ์ธ ์ฌ์ฉ์ ์ด๋ฒ์ด ์ฒ์์ด๋ผ๊ณ ํ ์ ์๊ธฐ ๋๋ฌธ์ ๋ค์ํ ๋ฌธ์ ์ฌํญ๋ค์ ๊ฒช์ ์ ์์์ต๋๋ค. ๐ฅ
์ด๋ฒ ๋ฏธ์ ์์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ฌ์ฉ์ ์ต๋ํ ์์ ํด์ผ ํ๊ธฐ ๋๋ฌธ์ ๋ผ์ฐํ ๊ธฐ๋ฅ ๋ํ ์ง์ ๊ตฌํํด์ผ ํ์ต๋๋ค. admin ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ํ์ด์ง๋ฅผ ๋ ๋๋งํ๊ธฐ ์ํด ๋ผ์ฐํฐ๋ฅผ ๊ตฌํํ์ต๋๋ค.
function Routes({ children }: { children: React.ReactNode }) {
const { path } = useContext(routerContext);
let currentRoute;
Children.forEach(children, (element: React.ReactNode) => {
if (!isValidChild(element)) {
invariant(isValidChild(element), `์ฌ๋ฐ๋ฅธ Route ์ปดํฌ๋ํธ๊ฐ ์๋๋๋ค.`);
return;
}
const { path: routePath, element: component } = element.props;
const [parsedPath, params] = extractPathAndParams(routePath);
if (isCurrentRoute(path, parsedPath)) {
currentRoute = component;
}
});
if (!currentRoute) {
return <></>;
}
return currentRoute;
}
<Routes>
<Route path="/admin" element={<Admin />} />
<Route path="/customer-order" element={<CustomerOrder />}/>
<Route path="/" element={<Entrance />} />
</Routes>
๋ผ์ฐํฐ๋ ๊ธฐ๋ณธ์ ์ธ ๋ฆฌ์กํธ ๋ผ์ฐํฐ์ ํํ๋ฅผ ํ ๋๋ก ๊ตฌํํ์ต๋๋ค. Routes ์ปดํฌ๋ํธ ๋ด์์ children์ผ๋ก ๋ค์ด์จ Route ์ปดํฌ๋ํธ๋ฅผ ์ํํ๊ณ ํ์ฌ path์ ๋ง๋ Route ์ปดํฌ๋ํธ๋ฅผ ์ฐพ์๋ด๋ ํ์์ ๋๋ค.
์ด๋ path๋ ๊ฐ์ด ๋ณํ ๋๋ง๋ค ๋ฆฌ๋ ๋๋ง์ด ๋ฐ์ํด์ผํ๊ธฐ ๋๋ฌธ์ Context API๋ฅผ ์ด์ฉํด ์ ๊ณตํฉ๋๋ค.
export const ROUTE_PARAMETER_REGEX = /:(\w+)/g;
export const URL_FRAGMENT_REGEXP = '([^\\/]+)';
export const QUERY_STRING_REGEXP = /\?[\w=&]+/g;
export function extractPathAndParams(routePath: string): [string, string[]] {
const params: string[] = [];
const parsedPath = routePath
.replace(ROUTE_PARAMETER_REGEX, (_match: string, paramName: string) => {
params.push(paramName);
return URL_FRAGMENT_REGEXP;
})
.replace(QUERY_STRING_REGEXP, '')
.replace(/\//g, '\\/');
return [parsedPath, params];
}
Route ์ปดํฌ๋ํธ๊ฐ ์ ๊ณตํ๋ path๊ฐ ๋ง์ฝ path parameter ํํ๋ก ์ฃผ์ด์ง๋ค๋ฉด ๊ทธ๊ฒ์ ์ ์ ํ ํํ๋ก ๊ฐ๊ณตํด์ผํฉ๋๋ค.
์ด๋ ์ฌ์ฉํ๋ ๊ฒ ์์ ์ ํธ ๋ฉ์๋์ด๋ฉฐ, path parameter๋ฅผ url fragment๋ก ๋ฐ๊ฟ์ค๋๋ค. url fragment๋ url๋ก ๋ค์ด์จ ๋ฌธ์์ด๊ณผ ๋งค์นญ๋๋ ์ ๊ท์์ ๋๋ค.
์ด๋ฒ ๋ฏธ์ ์์๋ path parameter๋ฅผ ์ฌ์ฉํ์ง ์์์ง๋ง ์ด๊ธฐ ๊ธฐํ์์๋ ์ถ๊ฐํ ํ์๊ฐ ์์๊ธฐ ๋๋ฌธ์ ๋ฏธ๋ฆฌ ๊ตฌํํ์ต๋๋ค.
์ด๋ฒ ๋ฏธ์ ์์๋ ๋ช ๊ฐ์ง ๋๊ด์ด ์์๋๋ฐ, ํ์ ์คํฌ๋ฆฝํธ์ ๋์ ๋ ํฐ ๋๊ด ์ค ํ๋์์ต๋๋ค. ์งํ ์ค์ ํ์ ์คํฌ๋ฆฝํธ์ ๋์ ์ ํํํ๋ ์ ์ด ๋ง์์ง๋ง, ์ธ์ ๊ฐ๋ ๊ฒช์ด์ผ ํ ์ผ์ด๋ผ๊ณ ์๊ฐํด ์ด๊ณผ ์๋ ฅ์ ๊ฒฌ๋ ์ต๋๋ค.
export interface StoreLoginType {
storeId: string;
password: string;
}
export interface StoreRegisterType {
storeId: string;
name: string;
password: string;
branchName: string;
}
export interface StoreInputsType {
storeId: string;
name: string;
password: string;
passwordConfirm: string;
branchName: string;
}
export interface StoreType {
id: number;
storeId: string;
name: string;
branchName: string;
}
export const initialStoreValue = {
id: 0,
storeId: '',
name: '',
branchName: '',
};
export const initialStoreInputsValue = {
storeId: '',
name: '',
password: '',
passwordConfirm: '',
branchName: '',
};
์ฒ์ ํ์ ์คํฌ๋ฆฝํธ๋ฅผ ์ฌ์ฉํ ๋ ํน์ ๊ฐ์ null์ด๋ undefined๋ก ์ด๊ธฐํํ๋ฉด ํด๋น ๊ฐ์ ์กด์ฌ ์ฌ๋ถ๋ฅผ ์ฒดํฌํ๋ ๋ถ๊ธฐ๊ฐ ํ์ํ์ต๋๋ค. Type Guard๋ฅผ ์ฌ์ฉํ๋ ๊ฒ ์ฝ๋์ ๊ฐ๋ ์ฑ์ ์ข์ง ์์ ์ํฅ์ ์ฃผ๋ ๊ฒ ๊ฐ์ ์ด๋ฅผ ํด๊ฒฐํ ๋ฐฉ๋ฒ์ ์๊ฐํ๋ค๊ฐ initialValue๋ฅผ ์ฃผ๋ ๋ฐฉ์์ ๋ ์ฌ๋ ธ์ต๋๋ค.
๊ฐ์ฒด ๊ฐ์ ํ์ ์ผ ๊ฒฝ์ฐ ๊ฐ ์์ฑ์ falsyํ ๊ฐ์ ์ค ์ด๊ธฐ ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด ํ ๋นํ๋ ๋ฐฉ์์ผ๋ก ๊ตฌํ์ ์งํํ๋๋ฐ, ๋์ ๊ฐ์ ์ด์ฉํ ๋ ๊ทธ ๊ฐ๋ค์ด ๋ชจ๋ validํ์ง ์ฌ๋ถ๋ฅผ ์ฒดํฌํด์ผํฉ๋๋ค.
๋ค๋ฅธ ๋ถ๋ค์ ํ์ ์คํฌ๋ฆฝํธ ์ฌ์ฉ๋ฒ๋ค์ ๋ณด๋ฉฐ ๋ ์ ์ ํ ๋ฐฉ๋ฒ์ด ์๋์ง ์์๋ด์ผ๊ฒ ์ต๋๋ค.
import { useContext, useEffect, useState } from 'react';
import categoryAPI from '../api/categoryAPI';
import { storeContext } from '../context/StoreProvider';
import { CategoryType, initialCategoryValue } from '../types/category';
const useCategory = () => {
const [categories, setCategories] = useState<CategoryType[]>([]);
const [selectedCategory, setSelectedCategory] = useState<CategoryType>({
...initialCategoryValue,
});
const { store } = useContext(storeContext);
useEffect(() => {
const getCategories = async () => {
if (store.id) {
const response = await categoryAPI.getCategoriesById(store.id);
setCategories(response);
setSelectedCategory(response[0] || { ...initialCategoryValue });
}
};
getCategories();
}, [store.id]);
return { categories, setCategories, selectedCategory, setSelectedCategory };
};
export default useCategory;
์ปค์คํ ํ ์ ๋ํ ์ง์์ ์์์ง๋ง, ๊ตณ์ด ์ฌ์ฉํ ๋งํ ํ๋ก์ ํธ๋ฅผ ํด๋ณธ ๊ฒฝํ์ด ์๊ธฐ ๋๋ฌธ์ ์ค์ ์ ์ธ ์ฌ์ฉ์ ์ด๋ฒ์ ์ฒ์์ด์์ต๋๋ค. ์ปค์คํ ํ ์ ์ฅ์ ์ ๋ก์ง์ ์ฌ์ฌ์ฉํ ์ ์๋ค๋ ์ ์ด ์๊ฒ ์ง๋ง, ๊ตฌํ์ ํ๋ฉด์ ๊ทธ๊ฒ๋ณด๋ค ๋ ๋งค๋ ฅ์ ์ธ ์ ์ ์ ์ ์์์ต๋๋ค.
// Admin.tsx
function Admin() {
const { categories, setCategories, selectedCategory, setSelectedCategory } =
useCategory();
const { products, setProducts } = useProduct(selectedCategory);
const { store } = useContext(storeContext);
const { changeAdminAuthority } = useContext(adminAuthorityContext);
const navigate = useNavigate();
.
.
.
.
๋ก์ง์ ์ถ์ํ ๊ณ์ธต์ ์ ๊ณตํด ์ธํฐํ์ด์ค์ฒ๋ผ ์ฌ์ฉํ ์ ์๋ค๋ ์ ์ ๋๋ค. ์ปดํฌ๋ํธ ๋ด๋ถ์์ ๋ชจ๋ ์์ ์ ํ ์ ์๊ฒ ์ง๋ง, ๊ทธ๋ด ๊ฒฝ์ฐ ํ๋์ ์ปดํฌ๋ํธ๊ฐ ๋๋ฌด ๋ง์ ์ฑ ์์ ๊ฐ์ง๊ฒ ๋ฉ๋๋ค.
์ปค์คํ ํ ์ ์ด์ฉํ ์ฑ ์์ ๋ถ๋ฆฌ๋ ์ถํ ์ ์ง๋ณด์๋ ๊ธฐ๋ฅ์ ์ถ๊ฐ๋ฅผ ๋งค์ฐ ์ฉ์ดํ๊ฒ ํด์ค๋๋ค. ์ฝ๋์ ๊ฐ๋ ์ฑ ์ธก๋ฉด์์๋ ๋ง์ ๋์์ ์ฃผ๋ ํ์ํ๋ค๋ฉด ์์ฃผ ์ฌ์ฉํ๋ ์ต๊ด์ ๊ฐ์ ธ์ผ๊ฒ ์ต๋๋ค.
๋ฐฑ์๋ ๊ตฌํ ์ด๊ธฐ์ TypeORM์ ์ฌ์ฉํด DB ๊ด๋ จ ์์ ์ ์ฒ๋ฆฌํ์ต๋๋ค. ํ์ง๋ง ์ผ๋ฐ์ ์ธ SQL๋ฌธ์ ๋ํ ์๋ จ๋๊ฐ ์์ง ๋ถ์กฑํ๋ค๊ณ ํ๋จํด, ๊ธฐ์กด ์ฝ๋๋ค์ ๋ชจ๋ ์ผ๋ฐ SQL๋ก ๋ฐ๊ฟจ์ต๋๋ค... ๐ช ๊ทธ๋์์ธ์ง ์ด๋ฒ ๋ฏธ์ ์ ์๊ฐ์ด ๋ง์ด ๋ถ์กฑํ์ต๋๋ค.
// mysql.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { MySQLService } from './mysql.service';
@Module({
imports: [ConfigModule],
providers: [MySQLService],
exports: [MySQLService],
})
export class MySQLModule {}
// mysql.service.ts
import { Injectable, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as mysql from 'mysql2';
@Injectable()
export class MySQLService implements OnModuleInit {
pool: mysql.Pool;
constructor(private configService: ConfigService) {
this.pool = mysql.createPool({
host: configService.get('DATABASE_HOST'),
port: configService.get<number>('DATABASE_PORT'),
user: configService.get('DATABASE_USERNAME'),
password: configService.get('DATABASE_PASSWORD'),
database: configService.get('DATABASE_DATABASE'),
});
}
async onModuleInit() {
const poolPromise = this.pool.promise();
await poolPromise.execute(`
CREATE TABLE IF NOT EXISTS STORE (
id INT PRIMARY KEY AUTO_INCREMENT,
storeId VARCHAR(30) NOT NULL,
name VARCHAR(20) NOT NULL,
password VARCHAR(20) NOT NULL,
branchName VARCHAR(20),
deletedAt DATETIME
)
`);
.
.
.
.
.
๋ค์๊ณผ ๊ฐ์ ํํ๋ก mysql์ ์ฐ๋์ํฌ ์ ์์ต๋๋ค. ์น์์ ์ฐธ๊ณ ๋ฅผ ์ํด ๊ฒ์ํ ์๋ฃ๋ค์ ๋ชจ๋ TypeORM์ ์ด์ฉํ๊ธฐ ๋๋ฌธ์ Nest์ ์์ง ์ต์ํ์ง ์์ ์ ์ฅ์์ mysql์ ์ผ๋ฐ์ ์ธ ๋ฐฉ์์ผ๋ก ์ฐ๋์ํค๋ ๊ฒ๋ ๋๊ด์ด์์ต๋๋ค.
ํ์ง๋ง ์ผ๋ฐ SQL๋ก ์์ฑํ๋ค๋ณด๋ ํ์ฌ ์์ ์ด ์ด๋ค ๊ตฌํ์ ํ๋์ง ๋์ฑ ๋ช ํํ๊ฒ ์ ์ ์์ด ์ข์ ๊ฒฝํ์ด์์ต๋๋ค.
์ด๋ฒ ๋ฏธ์ ์์ ํ๋ก ํธ์๋๋ณด๋ค ๋ฐฑ์๋ ๋ถ๋ถ์ ์๊ฐ์ ํจ์ฌ ๋ง์ด ์๋นํ์ต๋๋ค. NestJS ์์ฒด๊ฐ ์ฒ์์ด๊ธฐ๋ ํ๊ณ , ORM์ด๋ผ๋ ๋๊ตฌ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ๋ ์ฒ์์ด์๊ธฐ ๋๋ฌธ์ ๋๋ค.
Nest๋ Spring ํ๋ ์์ํฌ๋ฅผ JS ๋ฒ์ ์ผ๋ก ์ฎ๊ธด ๋๋์ ๋๋ค. DI, IOC, ๋ฐ์ฝ๋ ์ดํฐ ํจํด ๋ฑ ์์ํ ๊ฐ๋ ๋ค์ด ์๊ธฐ ๋๋ฌธ์ ์์ ์ ํ๋ฉด์๋ ์ง๊ธ ๋ฌด์จ ์์ ์ ํ๋์ง ๋ฉํ์ธ์ง๋ฅผ ์ ๋๋ก ํ ์ ์์์ต๋๋ค. ์ด๋ฐ ์ํฉ์์ TypeORM๊น์ง ๋์ ํ๋ ค๊ณ ํ๋ ๋จธ๋ฆฟ์์ด ๋๋ฌด ์ด์ง๋ฌ์ ์ต๋๋ค.
์์ ์ด ์ฌ์ฉ ์ค์ธ ๋๊ตฌ์ ์ ๋ฐ์ ์ธ ๊ฐ๋ ๊ณผ ์ฅ๋จ์ ๋ฑ์ ํ์ ํ๋ ๊ฒ๋ ์ค์ํ๊ฒ ๋ค๋ ์๊ฐ์ ํ๊ฒ ๋์์ต๋๋ค. ์ถํ ํฌ์คํ ์ ํตํด Nest์ ๊ดํ ๋ด์ฉ์ ์ ๋ฆฌํด์ผ๊ฒ ์ต๋๋ค.
๋ฌธ์ํ์ ์ค์์ฑํ๋ค๋ ๊ฒ์ ์์ง๋ง ๋ง์ ํ๊ธฐ์๋ ํญ์ ์ด๋ ค์ด ๊ฒ ๊ฐ์ต๋๋ค. ์๊ฐ์ ์ธ ๋ถ๋ถ๋ ๊ทธ๋ ๊ณ ์์ ์ด ์๊ฒ ๋์๊ฑฐ๋ ๋๋ ์ ์ ๊ธ๋ก ์ถ๋ ฅํ๋ค๋ ๊ฑด ์ฝ์ง ์์ ์์ ์ ๋๋ค.
ํน์ ์ ์ฝ์ด๋ ๋ฌธ์ ์ฌํญ์์ ์์ ์ด ์ด๋ค ๊ณผ์ ์ ํตํด ํด๊ฒฐํ๋์ง ๊พธ์คํ ๊ธฐ๋กํ๋ ๊ฑด ๋ค๋ฅธ ์ฌ๋์๊ฒ ๋ณธ์ธ์ด ์ด๋ค ์ฌ๋์ธ์ง ํํํ ์ ์๋ ์ฆ๊ฑฐ๋ผ๊ณ ์๊ฐํฉ๋๋ค.
์ํฉ๊ณผ ํด๊ฒฐ ๊ณผ์ , ์์ ์ด ์ ํํ ๋ฐฉ๋ฒ์ ์ฅ๋จ์ ๋ฑ ์ ์ฒด์ ์ธ ๊ณผ์ ์ ์ค๋ช ํ ์ค ์์์ผํ๋ค๋ ๊ฒ ์ฐธ ์ค์ํ ๊ฒ ๊ฐ์ต๋๋ค. ์ง๊ธ ๊ธ์ ๋ฏธ์ ์ด ๋๋๊ณ 2์ฃผ๊ฐ ์ง๋ ์์ ์ ๋๋ค.
์๋ฌด๋ฐ ์ ๋ณด๊ฐ ์๋ ์ํ์์ 2์ฃผ ์ ์ ๊ธฐ์ต์ ๊ธฐ๋กํ๋ ค๋ฉด ์ด๋ ค์ ๊ฒ ์ง๋ง ๋คํํ ๋ ธ์ ์ ํํํ ๊ธฐ๋กํด๋จ๊ธฐ ๋๋ฌธ์ ๋์์ด ๋๋ ๊ฒ ๊ฐ์ต๋๋ค.
์ด๋ฒ ๋ฏธ์ ์ ์ค์ค๋ก์๊ฒ ์ ๋ง ํฐ ๋์ ์ด์์ต๋๋ค. ๋๋ถ๋ถ์ด ์๋ก์ด ๋๊ตฌ๋ค์ด์๊ณ , ๊ธฐํ์ด ๋ถํฌ๋ช ํด ์ค์ค๋ก ์ด๋ค ๊ฒ๋ค์ ๊ตฌํํด์ผํ ์ง ๊ฒฐ์ ํด์ผํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
๋ฏธ์ ์ ์งํํ๋ฉด์ ์ง๊ธ๊น์ง ํด์๋ ๋ฐฉ์๊ณผ๋ ๋ค๋ฅด๊ฒ ๊ธฐ๋ฅ์ ๋ถ์ํ๊ณ ์ด์๋ฅผ ๋ฐ๊ธํ๋ ๊ณผ์ ์ ์๋ตํ๋๋ฐ, ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ธ์ง ๊ตฌํ์ ํ๋ฉด ํ ์๋ก ๋ญ๊ฐ ์๋ชป๋๋ค๋ ๋๋์ ๋ฐ์์ต๋๋ค.
๋ชจ๋ฌ์ ๊ตฌํํ๋ ๊ณผ์ ์์๋ ์๋ชป๋ ์ถ์ํ๋ก ์ธํด ๊ธฐ๋ฅ๋ค์ด ๋ณต์กํด์ก๊ณ ๊ทธ๋ฐ ๊ฒ๋ค์ ๋ฆฌํฉํ ๋งํ๋ ๊ณผ์ ์์ ๋ถํ์ํ๊ฒ ์๊ฐ์ ์๋นํ์ต๋๋ค.
์ด์งธ์ ํ๋ก์ ํธ์ ์งํ์ ๊ธฐ๋ฅ๋ถ์๊ณผ ์ด๊ธฐ ์ด์๋ฐ๊ธ ๋ฐ ์ค๊ณ๊ฐ ์ค์ํ์ง ๋ชธ์ ์ฒด๊ฐํ ์ ์์์ต๋๋ค. ํ๋ก๊ทธ๋๋ฐ์ ๋จ์ํ ์ฝ๋์ ์์ฑ์ด ์๋ ์ฃผ์ด์ง ์๊ตฌ์ฌํญ์ ์ฒ ์ ํ๊ฒ ๋ถ์ํ๊ณ ๊ทธ๊ฒ์ ๋ง๋๋ ๊ณผ์ ์ด๋ผ๋ ๋ง์๊ฐ์ง์ ์๊ฒจ์ผ๊ฒ ์ต๋๋ค.
nestjs ์ ๋ฆฌ ํฌ์คํ ๊ธฐ๋ํ ๊ฒ์ ~~ ๐ค