코딩캠프/내일배움캠프

[ TIL ] 01.20(금) 50일차

고랑E 2023. 1. 20. 23:26
728x90

기본적인 crud 예제 프로그램 만들기

//프로젝트 초기화

npm init -y

tsc -init

 

 

tsconfig.json

{
  // include : 컴파일할 파일 경로를 설정합니다. [ src폴더 하위의 모든 .ts 확장자를 가진 파일 ]
  "include": ["src/**/*.ts"],
  "compilerOptions": {
    "lib": ["ES2021","DOM"],
    // esModuleInterop 속성이 위의 코드 처럼 true로 설정될 경우, ES6 모듈 사양을 준수하여 CommonJS 모듈을 가져올 수 있게 됩니다.
    // e.g. typescript-test.ts => import express from "express"
    "esModuleInterop": true,
    // 타입스크립트가 모듈을 찾는방법을 지정
    "moduleResolution":"NodeNext",
    "target": "ES2021",
    // rootDir : 컴파일될 타입스크립트 코드들의 위치를 명시합니다. 
    // "rootDir": "src",
    // outDir : 컴파일 후 생성되는 js파일이 생성될 폴더명
    "outDir": "dist",
    // strictNullChecks
    "strictNullChecks": true,
    // 암시적 any 타입을 허용하지 않는다   
    "noImplicitAny": true
  }
}

 

 

필요 패키지 설치

npm i express dotenv

npm i -D typescript @types/node @types/express @types/dotenv

 

 

.env

PORT=6600

 

 

src 폴더 안에 index.ts

/**
 * Required External Modules
 */

import dotenv from "dotenv";
import express, { Request, Response } from "express";

dotenv.config();

/**
 * App Variables
 */

if (!process.env.PORT) {
  process.exit(1);
}

const PORT: number = parseInt(process.env.PORT as string, 10);

const app = express();

/**
 *  App Configuration
 */

app.use(express.json());

/**
 * Server Activation
 */

app.get("/", (req: Request, res: Response) => {
  res.send("Hi");
});

app.listen(PORT, () => {
  console.log(`Listening on port http://localhost:${PORT}`);
});

 

 

ts-node-dev 패키지 설치

// 파일 변경되면 자동으로 변경된 ts파일을 실행시켜줌
npm i -D ts-node-dev

 

package.json 수정

  • --transpile-only : 타입스크립트의 빠른 트랜스파일 모듈 사용
  • src/index.ts : 엔트리파일 지정
7번째줄

"test": "echo \"Error: no test specified\" && exit 1"
를
"dev": "ts-node-dev --transpile-only src/index.ts"

 

npm run dev 로 서버 실행

 

 

폴더 생성 - src/items

파일 생성 - src/items/item.interface.ts

 

item.interface.ts

기본이 되는 BaseItem 인터페이스와 BaseItem을 상속받을 Item 인터페이스를 만든다.

Item ⇒ 특정한 값을 검색하거나 삭제할때 사용

BaseItem ⇒ 아이템을 수정, 등록 할때 사용

export interface BaseItem {
  name: string;
  price: number;
  description: string;
  image: string;
}

export interface Item extends BaseItem {
  id: number;
}

 

 

items.interface.ts

Items 인터페이스는 DB를 사용하지 않아서 메모리에 저장하는 배열의 타입을 지정하기 위해 만들었습니다.

import { Item } from "./item.interface";

export interface Items {
  [key: number]: Item;
}

item.interface 로부터 Item을 가져고

Items 인터페이스 생성 후 내보냄

[key: number]: Item; 부분 해석은 아래 코드를 보면서 이해하면 쉽습니다.

key 값으로 넘버가 여러개 들어가고 값은 Item을 받는다 라고 생각 하시면 됩니다.

 

[key: number]: Item; 이러한 부분을 타입스크립트에서 인덱스 시그니처 라고 합니다.

[key: number]: Item; 부분 해석 

const items: Items = {
  1: { id: 1, name: "item1", price: 100, description: "This is item1", image: "item1.jpg" },
  2: { id: 2, name: "item2", price: 200, description: "This is item2", image: "item2.jpg" },
  3: { id: 3, name: "item3", price: 300, description: "This is item3", image: "item3.jpg" },
};

 

 

파일 생성 - src/items/items.service.ts

items.service.ts

/**
 * Data Model Interfaces
 */

import { BaseItem, Item } from "./item.interface";
import { Items } from "./items.interface";

/**
 * In-Memory Store
 */

let items: Items = {
  1: {
    id: 1,
    name: "Burger",
    price: 599,
    description: "Tasty",
    image: "https://cdn.auth0.com/blog/whatabyte/burger-sm.png",
  },
  2: {
    id: 2,
    name: "Pizza",
    price: 299,
    description: "Cheesy",
    image: "https://cdn.auth0.com/blog/whatabyte/pizza-sm.png",
  },
  3: {
    id: 3,
    name: "Tea",
    price: 199,
    description: "Informative",
    image: "https://cdn.auth0.com/blog/whatabyte/tea-sm.png",
  },
};

/**
 * Service Methods
 */

export const findAll = async (): Promise<Item[]> => Object.values(items);

export const find = async (id: number): Promise<Item> => items[id];

//매개변수로 {name, price, description, image} 객체를 받고
//생성된 아이템을 반환 합니다.
//new Date().valueOf(); ⇒ 현재시간을 유닉스 타임으로 반환합니다.
export const create = async (newItem: BaseItem): Promise<Item> => {
  const id = new Date().valueOf();

  items[id] = {
    id,
    ...newItem,
  };

  return items[id];
};


//아이템을 업데이트할 update 함수를 만듭니다.
//update 함수는 id와 업데이트할 값 을 받고 업데이트 후 
//업데이트된 값 또는 null을 반환합니다.
//아이템이 없을때 ⇒ null
//아이템이 있으면 ⇒ 업데이트된 아이템
export const update = async ( id: number,  itemUpdate: BaseItem ): Promise<Item | null> => {
  const item = await find(id);

  if (!item) {
    return null;
  }

  items[id] = { id, ...itemUpdate };

  return items[id];
};


//아이템을 삭제할 remove 함수를 만듭니다.
//이 함수에서도 삭제할 아이템이 없으면 null 반환 하고 
//삭제가되면 아무것도 반환하지 않습니다(void). 
export const remove = async (id: number): Promise<null | void> => {
  const item = await find(id);

  if (!item) {
    return null;
  }

  delete items[id];
};

 

 

파일 생성 - src/items/items.router.ts

items.router.ts

/**
 * Required External Modules and Interfaces
 */

import express, { Request, Response } from "express";
// * 로 해서 ./items.service 에 있는 export를 모두 불러온다.
import * as ItemService from "./items.service";
import { BaseItem, Item } from "./item.interface";

/**
 * Router Definition
 */

export const itemsRouter = express.Router();

/**
 * Controller Definitions
 */

// GET items

itemsRouter.get("/", async (req: Request, res: Response) => {
  try {
    const items: Item[] = await ItemService.findAll();

    res.status(200).send(items);
  } catch (e) {
    res.status(500).send(e.message);
  }
});

// GET items/:id

itemsRouter.get("/:id", async (req: Request, res: Response) => {
  try {
    const id: number = parseInt(req.params.id, 10);
    const item: Item = await ItemService.find(id);

    if (item) {
      return res.status(200).send(item);
    }

    res.status(404).send("item not found");
  } catch (e) {
    res.status(500).send(e.message);
  }
});

// POST items

itemsRouter.post("/", async (req: Request, res: Response) => {
  try {
    const item: BaseItem = req.body;

    const newItem: Item = await ItemService.create(item);

    res.status(201).json(newItem);
  } catch (e) {
    res.status(500).send(e.message);
  }
});

// PUT items/:id

itemsRouter.put("/:id", async (req: Request, res: Response) => {
  try {
    const id: number = parseInt(req.params.id, 10);
    const itemUpdate: Item = req.body;

    const existingItem: Item = await ItemService.find(id);

    if (existingItem) {
      const updatedItem = await ItemService.update(id, itemUpdate);
      return res.status(200).json(updatedItem);
    }

    const newItem = await ItemService.create(itemUpdate);

    res.status(201).json(newItem);
  } catch (e) {
    res.status(500).send(e.message);
  }
});

// DELETE items/:id

itemsRouter.delete("/:id", async (req: Request, res: Response) => {
  try {
    const id: number = parseInt(req.params.id, 10);
    const result: void | null = await ItemService.remove(id);

    if (result === null) {
      return res.status(404).send("item not found");
    }
    res.sendStatus(204);
  } catch (e) {
    res.status(500).send(e.message);
  }
});

 

 

index.ts 수정

// 라우터 추가
import { itemsRouter } from "./items/items.router";

//미들웨어 추가
app.use("/api/menu/items", itemsRouter);

 

 

서버 실행 후 api 테스트

# get all items
GET /api/menu/items

# get a single item using an id parameter
GET /api/menu/items/:id

# create an item
POST /api/menu/items

{
  "name": "Salad",
  "price": 499,
  "description": "Fresh",
  "image": "https://images.ctfassets.net/23aumh6u8s0i/5pnNAeu0kev0P5Neh9W0jj/5b62440be149d0c1a9cb84a255662205/whatabyte_salad-sm.png"
}

# update an item using an id parameter
PUT /api/menu/items/:id

{
  "name": "Salad",
  "price": 500,
  "description": "Fresh",
  "image": "https://images.ctfassets.net/23aumh6u8s0i/5pnNAeu0kev0P5Neh9W0jj/5b62440be149d0c1a9cb84a255662205/whatabyte_salad-sm.png"
}

# remove an item using an id parameter
DELETE /api/menu/items/:id