728x90
반응형

react-async는 우리가 지난 섹션에서 만들었던 useAsync와 비슷한 함수가 들어있는 라이브러리다.

이름은 똑같으나 사용법이 조금 다르다.

 

만약 매번 프로젝트를 만들 때 마다 직접 요청 상태 관리를 위한 커스텀 Hook을 만들기 귀찮으면

이 라이브러리를 사용하면 유용할 것이다.

 

먼저 react-async를 설치한다.

$ npm i react-async

 

import { useAsync } from "react-async"

const loadCustomer = async ({ customerId }, { signal }) => {
  const res = await fetch(`/api/customers/${customerId}`, { signal })
  if (!res.ok) throw new Error(res)
  return res.json()
}

const MyComponent = () => {
  const { data, error, isLoading } = useAsync({ promiseFn: loadCustomer, customerId: 1 })
  if (isLoading) return "Loading..."
  if (error) return `Something went wrong: ${error.message}`
  if (data)
    return (
      <div>
        <strong>Loaded some data:</strong>
        <pre>{JSON.stringify(data, null, 2)}</pre>
      </div>
    )
  return null
}

react-async의 useAsync 를 사용 할 때 파라미터로 넣는 옵션 객체에는 호출 할 함수 promiseFn 을 넣고, 파라미터도 필드 이름과 함께 costomerId를 넣어주어야 한다.

 

User 컴포넌트 전환

User 컴포넌트를 react-async의 useAsync 로 전환하자

import React from 'react';
import axios from 'axios';
import { useAsync } from 'react-async';

async function getUser({ id }) {
  const response = await axios.get(
    `https://jsonplaceholder.typicode.com/users/${id}`
  );
  return response.data;
}

function User({ id }) {
  const { data: user, error, isLoading } = useAsync({
    promiseFn: getUser,
    id,
    watch: id
  });

  if (isLoading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!user) return null;

  return (
    <div>
      <h2>{user.username}</h2>
      <p>
        <b>Email:</b> {user.email}
      </p>
    </div>
  );
}

export default User;

 

react-async를 사용할 때에는 프로미스를 반환하는 함수의 파라미터를 객체형태로 해주어야 한다.

async function getUser({ id }) {}

 

그리고 watch 값에 특정 값을 넣어주면 이 값이 바뀔 때마다 promiseFn에 넣은 함수를 다시 호출한다.

 

 

Users 컴포넌트 전환

Users.js

import React, { useState } from 'react';
import axios from 'axios';
import { useAsync } from 'react-async';
import User from './User';

async function getUsers() {
  const response = await axios.get(
    'https://jsonplaceholder.typicode.com/users'
  );
  return response.data;
}

function Users() {
  const [userId, setUserId] = useState(null);
  const { data: users, error, isLoading, reload } = useAsync({
    promiseFn: getUsers
  });

  if (isLoading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return <button onClick={reload}>불러오기</button>;
  return (
    <>
      <ul>
        {users.map(user => (
          <li
            key={user.id}
            onClick={() => setUserId(user.id)}
            style={{ cursor: 'pointer' }}
          >
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={reload}>다시 불러오기</button>
      {userId && <User id={userId} />}
    </>
  );
}

export default Users;

 

 

이전에 사용했던 skip처럼 나중에 데이터를 불러오려면 promiseFn 대신 deferFn, reload 대신 run 함수를 사용한다.

 

Users.js

import React, { useState } from 'react';
import axios from 'axios';
import { useAsync } from 'react-async';
import User from './User';

async function getUsers() {
  const response = await axios.get(
    'https://jsonplaceholder.typicode.com/users'
  );
  return response.data;
}

function Users() {
  const [userId, setUserId] = useState(null);
  const { data: users, error, isLoading, run } = useAsync({
    deferFn: getUsers
  });

  if (isLoading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return <button onClick={run}>불러오기</button>;

  return (
    <>
      <ul>
        {users.map(user => (
          <li
            key={user.id}
            onClick={() => setUserId(user.id)}
            style={{ cursor: 'pointer' }}
          >
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={run}>다시 불러오기</button>
      {userId && <User id={userId} />}
    </>
  );
}

export default Users;

 

 

정리 

  • 로그인 할 때는 최상위 컴포넌트에 쿠키를 state에 담고 상태를 관리해준다.(로그아웃, 프로필 수정)
  • 로그아웃 했을 경우(state가 null값이 될 때) 최상위에서 첫 화면 컴포넌트를 return 한다.
  • API가 필요한 경우 처음 페이지가 렌더링 될 때 useAsync로 state(리스트, 사진 등)를 만들어준다.
  • API 필요 없을 경우 처음 페이지 렌더링 될 때 useEffect를 사용해준다.
  • 추가, 수정, 삭제가 필요한 데이터의 경우 reducer를 만들어서 관리한다.(action으로 실행)
728x90
반응형
728x90
반응형

데이터를 요청해야 할 때마다 리듀서를 작성하는 것은 매우 번거롭다.

커스텀 Hook을 만들어서 요청 상태 관리 로직을 쉽게 재사용하는 방법을 알아보자.

src폴더 하위에 useAsync.js 파일을 만든다.

 

useAsync.js

import { useReducer, useEffect } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'LOADING':
      return {
        loading: true,
        data: null,
        error: null
      };
    case 'SUCCESS':
      return {
        loading: false,
        data: action.data,
        error: null
      };
    case 'ERROR':
      return {
        loading: false,
        data: null,
        error: action.error
      };
    default:
      throw new Error(`Unhandled action type: ${action.type}`);
  }
}

function useAsync(callback, deps = []) {
  const [state, dispatch] = useReducer(reducer, {
    loading: false,
    data: null,
    error: false
  });

  const fetchData = async () => {
    dispatch({ type: 'LOADING' });
    try {
      const data = await callback();
      dispatch({ type: 'SUCCESS', data });
    } catch (e) {
      dispatch({ type: 'ERROR', error: e });
    }
  };

  useEffect(() => {
    fetchData();
    // eslint 설정을 다음 줄에서만 비활성화
    // eslint-disable-next-line
  }, deps);

  return [state, fetchData];
}

export default useAsync;

 

useAsync 함수는 두가지 파라미터를 받아온다. 첫 번째 파라미터는 API 요청을 시작하는 함수이고, 두 번째 파라미터는 deps 인데 이 deps 값은 해당 함수 안에서 사용하는 useEffect 의 deps 로 설정된다.

 

나중에 우리가 사용 할 비동기 함수에서 파라미터가 필요하고, 그 파라미터가 바뀔 때 새로운 데이터를 불러오고 싶을 경우에 활욜 할 수 있다.

 

이제 이 Hook을 써보자.

 

Users.js

import React from 'react';
import axios from 'axios';
import useAsync from './useAsync';

// useAsync 에서는 Promise 의 결과를 바로 data 에 담기 때문에,
// 요청을 한 이후 response 에서 data 추출하여 반환하는 함수를 따로 만들었습니다.
async function getUsers() {
  const response = await axios.get(
    'https://jsonplaceholder.typicode.com/users'
  );
  return response.data;
}

function Users() {
  const [state, refetch] = useAsync(getUsers, []);

  const { loading, data: users, error } = state; // state.data 를 users 키워드로 조회

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return null;
  return (
    <>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={refetch}>다시 불러오기</button>
    </>
  );
}

export default Users;

  

버튼 클릭 시 불러오기

만약 특정 버튼을 눌렀을 경우에만 API를 요청하고 싶다면 이렇게 하면 된다.

useAsync.js

import { useReducer, useEffect } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'LOADING':
      return {
        loading: true,
        data: null,
        error: null
      };
    case 'SUCCESS':
      return {
        loading: false,
        data: action.data,
        error: null
      };
    case 'ERROR':
      return {
        loading: false,
        data: null,
        error: action.error
      };
    default:
      throw new Error(`Unhandled action type: ${action.type}`);
  }
}

function useAsync(callback, deps = [], skip = false) {
  const [state, dispatch] = useReducer(reducer, {
    loading: false,
    data: null,
    error: false
  });

  const fetchData = async () => {
    dispatch({ type: 'LOADING' });
    try {
      const data = await callback();
      dispatch({ type: 'SUCCESS', data });
    } catch (e) {
      dispatch({ type: 'ERROR', error: e });
    }
  };

  useEffect(() => {
    if (skip) return;
    fetchData();
    // eslint 설정을 다음 줄에서만 비활성화
    // eslint-disable-next-line
  }, deps);

  return [state, fetchData];
}

export default useAsync;

skip 파라미터의 기본 값을 false로 지정하고, 만약 이 값이 true라면 useEffect 에서 아무런 작업도 하지 않도록 설정해주었다.

 

Users.js

import React from 'react';
import axios from 'axios';
import useAsync from './useAsync';

// useAsync 에서는 Promise 의 결과를 바로 data 에 담기 때문에,
// 요청을 한 이후 response 에서 data 추출하여 반환하는 함수를 따로 만들었습니다.
async function getUsers() {
  const response = await axios.get(
    'https://jsonplaceholder.typicode.com/users'
  );
  return response.data;
}

function Users() {
  const [state, refetch] = useAsync(getUsers, [], true);

  const { loading, data: users, error } = state; // state.data 를 users 키워드로 조회

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return <button onClick={refetch}>불러오기</button>;
  return (
    <>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={refetch}>다시 불러오기</button>
    </>
  );
}

export default Users;

useAsync 의 세 번째 파라미터에 true를 넣어줬고, !users인 상황에 불러오기 버튼을 렌더링해주었다.

 

 

API에 파라미터가 필요한 경우

이번에는 API를 요청 할 때 파라미터가 필요한 경우에 어떻게 해야 하는지 알아보겠다.

 

User 라는 컴포넌트를 만들고, id값을 props로 받아와서

 

https://jsonplaceholder.typicode.com/users/1

이런 식으로 맨 뒤에  id를 넣어서 API를 요청할 것이다.

src 폴더에 User.js 를 생성 후 다음 코드를 작성해보자.

 

User.js

import React from 'react';
import axios from 'axios';
import useAsync from './useAsync';

async function getUser(id) {
  const response = await axios.get(
    `https://jsonplaceholder.typicode.com/users/${id}`
  );
  return response.data;
}

function User({ id }) {
  const [state] = useAsync(() => getUser(id), [id]);
  const { loading, data: user, error } = state;

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!user) return null;

  return (
    <div>
      <h2>{user.username}</h2>
      <p>
        <b>Email:</b> {user.email}
      </p>
    </div>
  );
}

export default User;

useAsync 를 사용할 때 파라미터를 포함시켜서 함수를 호출하는 새로운 함수를 만들어서 등록한다.

그리고 id가 바뀔 때마다 재호출 되도록 deps에 id를 추가한다.

 

그 다음 Users.js에서 useState를 사용하여 userId의 상태를 관리한다. 초깃값은 null이며 리스트에 항목을

클릭할 때마다 사용자의 id를 userId 값으로 설정해준다.

 

Users.js

import React, { useState } from 'react';
import axios from 'axios';
import useAsync from './useAsync';
import User from './User';

// useAsync 에서는 Promise 의 결과를 바로 data 에 담기 때문에,
// 요청을 한 이후 response 에서 data 추출하여 반환하는 함수를 따로 만들었습니다.
async function getUsers() {
  const response = await axios.get(
    'https://jsonplaceholder.typicode.com/users'
  );
  return response.data;
}

function Users() {
  const [userId, setUserId] = useState(null);
  const [state, refetch] = useAsync(getUsers, [], true);

  const { loading, data: users, error } = state; // state.data 를 users 키워드로 조회

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return <button onClick={refetch}>불러오기</button>;
  return (
    <>
      <ul>
        {users.map(user => (
          <li
            key={user.id}
            onClick={() => setUserId(user.id)}
            style={{ cursor: 'pointer' }}
          >
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={refetch}>다시 불러오기</button>
      {userId && <User id={userId} />}
    </>
  );
}

export default Users;

 

실행해보면

 

 

id를 클릭할 때마다 useState로 선언된 userId가 바뀌게 되고, User 컴포넌트에 userId가 할당되면서 렌더리이 되는 것을

확인할 수 있다.

728x90
반응형
728x90
반응형

이번에는 useState 대신에 useReducer로 구현해보도록 하겠다.

 

Users.js

import React, { useEffect, useReducer } from 'react';
import axios from 'axios';

function reducer(state, action) {
  switch (action.type) {
    case 'LOADING':
      return {
        loading: true,
        data: null,
        error: null
      };
    case 'SUCCESS':
      return {
        loading: false,
        data: action.data,
        error: null
      };
    case 'ERROR':
      return {
        loading: false,
        data: null,
        error: action.error
      };
    default:
      throw new Error(`Unhandled action type: ${action.type}`);
  }
}

function Users() {
  const [state, dispatch] = useReducer(reducer, {
    loading: false,
    data: null,
    error: null
  });

  const fetchUsers = async () => {
    dispatch({ type: 'LOADING' });
    try {
      const response = await axios.get(
        'https://jsonplaceholder.typicode.com/users'
      );
      dispatch({ type: 'SUCCESS', data: response.data });
    } catch (e) {
      dispatch({ type: 'ERROR', error: e });
    }
  };

  useEffect(() => {
    fetchUsers();
  }, []);

  const { loading, data: users, error } = state; // state.data 를 users 키워드로 조회

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return null;
  return (
    <>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={fetchUsers}>다시 불러오기</button>
    </>
  );
}

export default Users;

 

useReducer 로 구현했을 때의 장점은 useState를 여러번 사용하지 않아도 되며 로직을 분리했으니 다른곳에서도 쉽게 재사용을 할 수 있다는 점이다.

728x90
반응형
728x90
반응형

API 연동의 기본

 

API 연동을 하기 위해 우선 프로젝트를 새로 만들어주자.

$ npx create-react-app react-api

 

그리고 API를 호출하기 위해 axios 라는 라이브러리를 설치한다.

$ npm i axios

 

API의 사용법은 다음과 같다.

import axios from 'axios';

axios.get('/users/1');

 

axios.post() 로 데이터를 등록 할 때에는 두 번째 파라미터에 등록하고자 하는 정보를 넣을 수 있다.

axios.post('/users', {
  username: 'blabla',
  name: 'blabla'
});

 

useState 와 useEffect 로 데이터 로딩하기

 

컴포넌트가 랜더링되는 시점에 요청을 시작하는 작업을 해보자.

요청에 대한 상태를 관리 할 때에는 다음과 같이 총 3가지 상태를 관리해주어야 한다.

  1. 요청의 결과
  2. 로딩 상태
  3. 에러

 

src 컴포넌트에 Users.js를 생성하고 다음 코드를 작성해보자.

 

Users.js

import React, { useState, useEffect } from 'react';
import axios from 'axios';

function Users() {
  const [users, setUsers] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchUsers = async () => {
      try {
        // 요청이 시작 할 때에는 error 와 users 를 초기화하고
        setError(null);
        setUsers(null);
        // loading 상태를 true 로 바꿉니다.
        setLoading(true);
        const response = await axios.get(
          'https://jsonplaceholder.typicode.com/users'
        );
        setUsers(response.data); // 데이터는 response.data 안에 들어있습니다.
      } catch (e) {
        setError(e);
      }
      setLoading(false);
    };

    fetchUsers();
  }, []);

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return null;
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>
          {user.username} ({user.name})
        </li>
      ))}
    </ul>
  );
}

export default Users;

 

사용자들이 담길 users와 loading상태 여부를 체크할 loading이랑, error 를 출력해주는 error를 선언한다.

  const [users, setUsers] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

 

그다음 주석대로 error와 users를 초기화하고 loading을 true로 만들어준 다음, 데이터가 정상적으로 수신되면 loading을 false로 바꿔준다. catch부분에 error를 담아준다.

  useEffect(() => {
    const fetchUsers = async () => {
      try {
        // 요청이 시작 할 때에는 error 와 users 를 초기화하고
        setError(null);
        setUsers(null);
        // loading 상태를 true 로 바꿉니다.
        setLoading(true);
        const response = await axios.get(
          'https://jsonplaceholder.typicode.com/users'
        );
        setUsers(response.data); // 데이터는 response.data 안에 들어있습니다.
      } catch (e) {
        setError(e);
      }
      setLoading(false);
    };

    fetchUsers();
  }, []);

 

 

컴포넌트가 잘 작동하는지 확인하기 위해 App 컴포넌트에 User 컴포넌트를 추가하자.

 

 App.js

import React from 'react';
import Users from './Users';

function App() {
  return <Users />;
}

export default App;

 

이제 주소로 들어가보면

 

 

 

로딩중인지, 에러가 발생했는지, user가 존재하지 않은지에 대해 조건문을 설정해준다.

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return null;

 

 

에러 발생까지 확인해준다. 주소를 이상하게 바꿔준다.

const response = await axios.get(
  'https://jsonplaceholder.typicode.com/users/showmeerror'
);

 

확인해보면

 

 

이번엔 버튼을 눌러서  API를 재요청하는 코드를 구현해보자.

 

Users.js

import React, { useState, useEffect } from 'react';
import axios from 'axios';

function Users() {
  const [users, setUsers] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const fetchUsers = async () => {
    try {
      // 요청이 시작 할 때에는 error 와 users 를 초기화하고
      setError(null);
      setUsers(null);
      // loading 상태를 true 로 바꿉니다.
      setLoading(true);
      const response = await axios.get(
        'https://jsonplaceholder.typicode.com/users'
      );
      setUsers(response.data); // 데이터는 response.data 안에 들어있습니다.
    } catch (e) {
      setError(e);
    }
    setLoading(false);
  };

  useEffect(() => {
    fetchUsers();
  }, []);

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return null;
  return (
    <>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={fetchUsers}>다시 불러오기</button>
    </>
  );
}

export default Users;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

참조

https://react.vlpt.us/integrate-api/01-basic.html

 

1. API 연동의 기본 · GitBook

1. API 연동의 기본 API 연동을 하기 위해서, 우선 프로젝트를 새로 만들어주도록 하겠습니다. $ npx create-react-app api-integrate 그리고, API 를 호출하기 위해서 axios 라는 라이브러리를 설치하세요. $ cd a

react.vlpt.us

 

728x90
반응형
728x90
반응형

지난글에 이어서 PATCH와 DELETE를 구현해보자.

 

먼저 npm 모듈을 설치하고 UpdateCatDto를 만들어준다.

 

$ npm i @nestjs/mapped-types

 

update-cat.dto.ts

import {IsString, IsNumber} from 'class-validator'
import { PartialType } from '@nestjs/mapped-types';
import { CreateCatDto } from './create-cat.dto';

export class UpdateCatDto extends PartialType(CreateCatDto) {}

 

Update 먼저 구현해보자.  컨트롤러와 서비스를 바꿔준다.

 

cats.controller.ts

import { Controller, Get, Param, Post, Delete, Patch, Body, Query } from '@nestjs/common';
import { CatsService } from './cats.service';
import { CreateCatDto } from './dto/create-cat.dto';
import { UpdateCatDto } from './dto/update-cat.dto';
import { Cat } from './schemas/cat.schema';


@Controller('cats')
export class CatsController {
    constructor(private readonly catsService: CatsService) {}

    @Get()
    async getAll(): Promise<Cat[]> {
        return await this.catsService.getAll();
    }

    @Get('/:id')
    async getOne(@Param('id') catId: number): Promise<Cat[]> {
        console.log(catId)
        return await this.catsService.getOne(catId);
    }

    @Post()
    async create(@Body() catsData : CreateCatDto) {
        return await this.catsService.create(catsData);
    }

    @Patch('/:id')
    async update(@Param('id') catId: number, @Body() updateData: UpdateCatDto) {
        return this.catsService.update(catId,updateData)
    }

}

 

cats.service.ts

import { Model } from 'mongoose';
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Cat, CatDocument } from './schemas/cat.schema';
import { CreateCatDto } from './dto/create-cat.dto';
import { UpdateCatDto } from './dto/update-cat.dto';

@Injectable()
export class CatsService {
  constructor(@InjectModel(Cat.name) private catModel: Model<CatDocument>) {}

  async getAll(): Promise<Cat[]> {
    return await this.catModel.find().exec();
  }

  async getOne(id:number): Promise<Cat[]> {
    return await this.catModel.find({"id" : id});
  } 

  async create(catsData: CreateCatDto) {
    let latestId = await this.catModel.findOne();
    return await this.catModel.create({...catsData, id : parseInt(latestId ? latestId.id : 0)+1});
  }

  async update(id: number, updateData : UpdateCatDto) {
    try {
      await this.catModel.where({"id" : id}).update(updateData);
      return true
    }
    catch(e) {
      return false
    }
  }
}

PATCH 메소드로 요청을 보내보자.


 GetAll()로 확인해보자.

 

정상적으로 id가 1인 데이터의 name이 "초코"로 바뀐 것을 확인할 수 있다.

 

 

DELETE도 만들어보자.

 

cats.controller.ts

import { Controller, Get, Param, Post, Delete, Patch, Body, Query } from '@nestjs/common';
import { CatsService } from './cats.service';
import { CreateCatDto } from './dto/create-cat.dto';
import { UpdateCatDto } from './dto/update-cat.dto';
import { Cat } from './schemas/cat.schema';


@Controller('cats')
export class CatsController {
    constructor(private readonly catsService: CatsService) {}

    @Get()
    async getAll(): Promise<Cat[]> {
        return await this.catsService.getAll();
    }

    @Get('/:id')
    async getOne(@Param('id') catId: number): Promise<Cat[]> {
        console.log(catId)
        return await this.catsService.getOne(catId);
    }

    @Post()
    async create(@Body() catsData : CreateCatDto) {
        return await this.catsService.create(catsData);
    }

    @Patch('/:id')
    async update(@Param('id') catId: number, @Body() updateData: UpdateCatDto) {
        return this.catsService.update(catId,updateData)
    }

    @Delete('/:id')
    async delete(@Param('id') catId: number) {
        return this.catsService.delete(catId);
    }

}

 

cats.service.ts

import { Model } from 'mongoose';
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Cat, CatDocument } from './schemas/cat.schema';
import { CreateCatDto } from './dto/create-cat.dto';
import { UpdateCatDto } from './dto/update-cat.dto';

@Injectable()
export class CatsService {
  constructor(@InjectModel(Cat.name) private catModel: Model<CatDocument>) {}

  async getAll(): Promise<Cat[]> {
    return await this.catModel.find().exec();
  }

  async getOne(id:number): Promise<Cat[]> {
    return await this.catModel.find({"id" : id});
  } 

  async create(catsData: CreateCatDto) {
    let latestId = await this.catModel.findOne();
    return await this.catModel.create({...catsData, id : parseInt(latestId ? latestId.id : 0)+1});
  }

  async update(id: number, updateData : UpdateCatDto) {
    try {
      await this.catModel.where({"id" : id}).update(updateData);
      return true
    }
    catch(e) {
      return false
    }
  }

  async delete(id: number) {
    try {
      await this.catModel.remove({"id" : id});
      return true
    }
    catch(e) {
      return false
    }
  }
}

 


postman으로 실행해본다.


getAll() 전체데이터를 확인해본다.

 

 

이렇게 몽고DB로 연결해서 데이터 CRUD 하는 것을 완성하였다.

728x90
반응형

'Back-End > Nest.js' 카테고리의 다른 글

Nest.js | Middleware  (0) 2021.10.28
Nest.js | Swagger  (0) 2021.10.28
Nest.js | MongoDB | Create  (0) 2021.10.06
Nest.js | MongoDB | Find  (0) 2021.10.06
Nest.js | MongoDB | Model,Schema,Controller  (0) 2021.10.06
728x90
반응형

먼저 main.ts 파일에 pipe를 추가한다.

 

main.ts

import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist : true, 
      forbidNonWhitelisted : true,
      transform : true
    })
  )
  await app.listen(3000);
}
bootstrap();

 

그다음 컨트롤러와 서비스를 바꿔주자. GetOne 메소드도 추가하였다.

 

cats.cantroller.ts

import { Controller, Get, Param, Post, Delete, Patch, Body, Query } from '@nestjs/common';
import { CatsService } from './cats.service';
import { CreateCatDto } from './dto/create-cat.dto';
import { Cat } from './schemas/cat.schema';


@Controller('cats')
export class CatsController {
    constructor(private readonly catsService: CatsService) {}

    @Get()
    async getAll(): Promise<Cat[]> {
        return await this.catsService.getAll();
    }

    @Get('/:id')
    async getOne(@Param('id') catId: number): Promise<Cat[]> {
        console.log(catId)
        return await this.catsService.getOne(catId);
    }

    @Post()
    async create(@Body() catsData : CreateCatDto) {
        return await this.catsService.create(catsData);
    }

}

 

cats.service.ts

import { Model } from 'mongoose';
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Cat, CatDocument } from './schemas/cat.schema';
import { CreateCatDto } from './dto/create-cat.dto';

@Injectable()
export class CatsService {
  constructor(@InjectModel(Cat.name) private catModel: Model<CatDocument>) {}

  async getAll(): Promise<Cat[]> {
    return await this.catModel.find().exec();
  }

  async getOne(id:number) {
    return await this.catModel.find({"id" : id});
  } 

  async create(catsData: CreateCatDto) {
    let latestId = await this.catModel.findOne();
    return await this.catModel.create({...catsData, id : parseInt(latestId ? latestId.id : 0)+1});
  }
}

생성할 때 id가 최근의 id +1 이 되게끔 하였다.

 

2번 생성한 뒤 postman으로 getAll()을 확인해보자.

 

 

 

mongoose에서 Create는 추가한 값을 그대로 반환해준다. 아주 성공적으로 id가 +1씩 되면서 잘 되었다.

 

 

배열 안에 잘 담겨서 반환된 것을 확인할 수 있다.

728x90
반응형

'Back-End > Nest.js' 카테고리의 다른 글

Nest.js | Swagger  (0) 2021.10.28
Nest.js | MongoDB | PATCH and DELETE  (0) 2021.10.06
Nest.js | MongoDB | Find  (0) 2021.10.06
Nest.js | MongoDB | Model,Schema,Controller  (0) 2021.10.06
Nest.js | MongoDB | Schema  (0) 2021.10.06
728x90
반응형

먼저 다음과 같이 main.ts 파일에 파이프를 추가해준다.

 

main.ts

import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist : true, 
      forbidNonWhitelisted : true,
      transform : true
    })
  )
  await app.listen(3000);
}
bootstrap();

 

그다음 controller와 service에 GET메소드로 getAll() 메소드를 만들어보자.

 

cats.controller.ts

import { Controller, Get, Param, Post, Delete, Patch, Body, Query } from '@nestjs/common';
import { CatsService } from './cats.service';
import { CreateCatDto } from './dto/create-cat.dto';
import { Cat } from './schemas/cat.schema';


@Controller('cats')
export class CatsController {
    constructor(private readonly catsService: CatsService) {}

    @Get()
    async getAll(): Promise<Cat[]> {
        return await this.catsService.getAll();
    }


}

 

cats.service.ts

import { Model } from 'mongoose';
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Cat, CatDocument } from './schemas/cat.schema';
import { CreateCatDto } from './dto/create-cat.dto';

@Injectable()
export class CatsService {
  constructor(@InjectModel(Cat.name) private catModel: Model<CatDocument>) {}

  async getAll(): Promise<Cat[]> {
    return await this.catModel.find().exec();
  }
}

 

postman으로 확인해보자

 

 

아직 추가한 Document가 없기 때문에 당연히 빈배열로 받아와진다.

728x90
반응형

'Back-End > Nest.js' 카테고리의 다른 글

Nest.js | MongoDB | PATCH and DELETE  (0) 2021.10.06
Nest.js | MongoDB | Create  (0) 2021.10.06
Nest.js | MongoDB | Model,Schema,Controller  (0) 2021.10.06
Nest.js | MongoDB | Schema  (0) 2021.10.06
Nest.js | E2E TESTING | PATCH and DELETE  (0) 2021.09.30
728x90
반응형

기본적인 cats 모듈,컨트롤러,서비스를 만들고 schemas 폴더를 다음과 같이 옮긴다.

$ nest g module cats
$ nest g co cats
$ nest g s cats

 

 

cats.module.ts 파일을 수정해준다.

cats.module.ts

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
import { Cat, CatSchema } from './schemas/cat.schema';

@Module({
  imports: [MongooseModule.forFeature([{ name: Cat.name, schema: CatSchema }])],
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}

 

그 다음 지정된 스키마를 @InjectModel() 데코레이터를 사용하여 CatService에 삽입한다.

cats.service.ts

import { Model } from 'mongoose';
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Cat, CatDocument } from './schemas/cat.schema';
import { CreateCatDto } from './dto/create-cat.dto';

@Injectable()
export class CatsService {
  constructor(@InjectModel(Cat.name) private catModel: Model<CatDocument>) {}

}

 

아직 DTO파일을 안 만들었기 때문에 에러가 뜬다. DTO파일을 작성해보자.

 

$ npm i class-validator class-transformer

 

 

create-cat.dto.ts

import {IsString, IsNumber} from 'class-validator'

export class CreateCatDto {
    @IsString()
    readonly name: string;

    @IsNumber()
    readonly age: number;
    
    @IsString({each:true})
    readonly breed: string;
}

 

이제 Contoller를 작성해보자.

 

cat.controller.ts

import { Controller, Get } from '@nestjs/common';
import { CatsService } from './cats.service';
import { Cat } from './schemas/cat.schema';


@Controller('cats')
export class CatsController {
    constructor(private readonly catsService: CatsService) {}

}

 

여기까지 cats모듈과 dto를 만들어보았다.

 

728x90
반응형

'Back-End > Nest.js' 카테고리의 다른 글

Nest.js | MongoDB | Create  (0) 2021.10.06
Nest.js | MongoDB | Find  (0) 2021.10.06
Nest.js | MongoDB | Schema  (0) 2021.10.06
Nest.js | E2E TESTING | PATCH and DELETE  (0) 2021.09.30
Nest.js | E2E TESTING | Testing GET movies id  (0) 2021.09.30
728x90
반응형

이 글은 NestJS document 사이트 기반으로 작성했습니다.

 

https://docs.nestjs.kr/techniques/mongodb

 

네스트JS 한국어 매뉴얼 사이트

네스트JS 한국, 네스트JS Korea 한국어 매뉴얼

docs.nestjs.kr

 

몽고DB설치하는 법은 아래 링크에서 확인 부탁드립니다.

 

https://typo.tistory.com/entry/MongoDB%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0%EC%82%AD%EC%A0%9C%ED%95%98%EA%B8%B0?category=866148

 

MongoDB | 설치하기, 삭제하기

1.    몽고DB의 public GPG key 주입 ubuntu@dev:~$ wget -qO - https://www.mongodb.org/static/pgp/server-4.4.asc | sudo apt-key add - OK 2.    몽고DB를 위한 리스트 파일 생성 ubuntu@dev:~$ echo "d..

typo.tistory.com

 

mongoDB가 준비되었다면 이제 mongoose를 프로젝트 폴더에 설치해준다.

$ npm install --save @nestjs/mongoose mongoose

 

 

이제 MongooseModule을 루트 AppModule로 가져올 수 있다. ( id와 패스워드는 각자 설정한대로)

forRoot() 메소드는 Mongoose 패키지의 mongoose.connect()와 동일한 구성 객체를 허용한다.

 

app.module.ts

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';

@Module({
  imports: [MongooseModule.forRoot('mongodb://test:test1234@localhost:27017/admin')],
})
export class AppModule {}

 

Mongoose에서는 모든 것이 스키마에서 파생된다. 스키마는 모델을 정의하는 데 사용된다.

schemas 폴더를 만들고 cat.schema.ts 파일을 작성해보자.

 

/src/cat/schemas/cat.schema.ts

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';

export type CatDocument = Cat & Document;

@Schema()
export class Cat {
  @Prop()
  id: number;

  @Prop()
  name: string;

  @Prop()
  age: number;

  @Prop()
  breed: string;
}

export const CatSchema = SchemaFactory.createForClass(Cat);

 

@Props() 데코레이터는 문서의 속성을 정의한다. 예를 들어 다음과 같이 정의할 수 있다.

@Prop([String])
tags: string[];

 

또는 사용 가능한 옵션에 대한 옵션 객체 인수를 받는다. 기본값을 지정하거나 속성이 필요한지 여부를 나타낼 수 있다.

@Prop({ required: true })
name: string;

 

또는 데코레이터를 사용하지 않음을 선호하는 경우 스키마를 수동으로 정의할 수 있다.

export const CatSchema = new mongoose.Schema({
  name: String,
  age: Number,
  breed: String,
});

 

 

728x90
반응형
728x90
반응형

전체 코드 (index.ts)

import * as CryptoJS from "crypto-js"

class Block {
    public index: number;
    public hash: string;
    public previousHash: string;
    public data: string;
    public timestamp: number;

    static calculateBlockHash = (
        index: number,
        previousHash: string,
        timestamp: number,
        data: string
    ): string => CryptoJS.SHA256(index + previousHash + timestamp + data).toString();

    static validateStructure = (aBlock: Block): boolean =>
        typeof aBlock.index === "number" &&
        typeof aBlock.hash === "string" &&
        typeof aBlock.previousHash === "string" &&
        typeof aBlock.timestamp === "number" &&
        typeof aBlock.data === "string";

    constructor(
        index: number,
        hash: string,
        previousHash: string,
        data: string,
        timestamp: number
    ) {
        this.index = index;
        this.hash = hash;
        this.previousHash = previousHash;
        this.data = data;
        this.timestamp = timestamp;
    }
}

const TeepoBlock: Block = new Block(0, "2020202020202", "", "Hello", 123456);

let blockchain: Block[] = [TeepoBlock];

const getBlockchain = (): Block[] => blockchain;

const getLatestBlock = (): Block => blockchain[blockchain.length - 1];

const getNewTimeStamp = (): number => Math.round(new Date().getTime() / 1000);

const createNewblock = (data: string): Block => {
    const previousBlock: Block = getLatestBlock();
    const newIndex: number = previousBlock.index + 1;
    const newTimestamp: number = getNewTimeStamp();
    const newHash: string = Block.calculateBlockHash(
        newIndex,
        previousBlock.hash,
        newTimestamp,
        data
        );
    const newBlock: Block = new Block(
        newIndex,
        newHash,
        previousBlock.hash,
        data,
        newTimestamp
        );
    addBlock(newBlock);
    return newBlock
}

const getHashforBlock = (aBlock: Block): string =>
    Block.calculateBlockHash(
        aBlock.index,
        aBlock.previousHash,
        aBlock.timestamp,
        aBlock.data
    )

const isBlockValid = (
    candidateBlock: Block,
    previousBlock: Block
): boolean => {
    if (!Block.validateStructure(candidateBlock)) {
        return false;
    } else if (previousBlock.index + 1 !== candidateBlock.index) {
        return false;
    } else if (previousBlock.hash !== candidateBlock.previousHash) {
        return false;
    } else if (getHashforBlock(candidateBlock) !== candidateBlock.hash) {
        return false;
    } else {
        return true
    }

}

const addBlock = (candidateBlock: Block): void => {
    if (isBlockValid(candidateBlock, getLatestBlock())) {
        blockchain.push(candidateBlock)
    }
}

createNewblock("second Block");
createNewblock("third Block");
createNewblock("fourth Block");

console.log(blockchain)

export { };

 

 

 

addBlock을 createNewblock 부분에 추가해준다.

const createNewblock = (data: string): Block => {
    const previousBlock: Block = getLatestBlock();
    const newIndex: number = previousBlock.index + 1;
    const newTimestamp: number = getNewTimeStamp();
    const newHash: string = Block.calculateBlockHash(
        newIndex,
        previousBlock.hash,
        newTimestamp,
        data
        );
    const newBlock: Block = new Block(
        newIndex,
        newHash,
        previousBlock.hash,
        data,
        newTimestamp
        );
    addBlock(newBlock);
    return newBlock
}

 

 

함수를 실행해준다.

createNewblock("second Block");
createNewblock("third Block");
createNewblock("fourth Block");

 

 

tsc-watch 로그를 보면..!

 

해시 링크드 리스트가 완성되었다!

728x90
반응형

+ Recent posts