티스토리 뷰

728x90
반응형

이미 수행기간이 끝난 프로젝트이지만, 선택항목 중 하나이자, 수행기간 때는 못했던 기능을 도전해보려고 한다.
외부 OpenAPI를 이용해서 게시글을 업로드한 시점의 날씨 정보를 포함하는 기능을 추가하여 고도화하기로 했다.
외부 API를 활용하기 위해 URL을 Fetching하거나 Parsing 하려면 Axios를 사용해야된다. 그러면 Axios가 무엇인지 소개를 하며
고민과 문제해결과정을 포스팅하고자한다. (물론 Javascript에 fetch함수도 있다!)

Axios 란?

- axios 정의

  • Axios는 브라우저, Node.js 를 위한 Promise API를 활용하는 HTTP 비동기통신 라이브러리 이다.
  • 프레임워크에서 ajax를 구현할 때 axios를 쓰는 편이다.
  • Axios는 운영환경에 따라 브라우저의 XMLHttpRequest 객체 또는 Node.js의 Http API 를 사용한다.


- commonJS에서 지원해주는 fetch() 함수와 axios 의 차이점은?

  • 간단하게 사용할 때는 fetch() 를 사용하고, 확장성을 염두한다면 Axios를 사용하면 좋다.


[ axios 공식 문서 ]

Getting Started | Axios Docs

Getting Started Promise based HTTP client for the browser and node.js What is Axios? Axios is a promise-based HTTP Client for node.js and the browser. It is isomorphic (= it can run in the browser and nodejs with the same codebase). On the server-side it u

axios-http.com


NestJS에 Axios 적용하기

(1) 패키지 설치

$ npm i --save @nestjs/axios



(2) posts.module 에 HttpModule 넣기

// posts.module.ts

import { Module } from '@nestjs/common';
import { PostsService } from './posts.service';
import { PostsController } from './posts.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Posts } from '../entities/Posts';
import { Users } from '../entities/Users';
import { PostsRepository } from './posts.repository';
import { ConfigModule } from '@nestjs/config';
import { HttpModule } from '@nestjs/axios';

@Module({
  imports: [
    TypeOrmModule.forFeature([Posts, Users]),
    HttpModule, // axios 를 사용하기 위한 HttpModule 넣기
    ConfigModule,
  ],
  controllers: [PostsController],
  providers: [PostsService, PostsRepository],
})
export class PostsModule {}



(3) posts.service.ts 에 HttpService 주입하기

// posts.service.ts

import { InsertResult } from 'typeorm';
import { HttpService } from '@nestjs/axios';
import { ConfigService } from '@nestjs/config';
import { PostsRepository } from './posts.repository';


@Injectable()
export class PostsService {
  constructor(
    // axios 를 사용하기 위한 HttpService 를 주입
    private axiosHttpService: HttpService,

    // postRepository 주입
    private readonly postsRepository: PostsRepository,

    // configModule주입
    private readonly configService: ConfigService,
  ) {}
  
  ...
}


현재 클라이언트의 IP주소 호출하여, 현재 IP주소에 위치한 지역의 날씨를 구하기

필자가 사용하는 OpenAPI 는 2주 동안 무료로 날씨정보를 제공하는 오픈 API 인 Weather API 이다.

Weather API - WeatherAPI.com

Quick and Easy Signup for Weather API WeatherAPI.com makes it super easy to integrate our realtime, daily, hourly and 15 min interval weather forecast data, historical weather, air quality data, autocomplete, time zone, astronomy and sports data into your

www.weatherapi.com



여담이지만 Weather API 보다는... 다른 날씨 API를 사용해보는걸 추천한다.
왜냐하면 Busan을 검색했을 때 한국의 부산이 아닌, 파키스탄의 부산 날씨가 나왔다.

// 요청
http://api.weatherapi.com/v1/current.json?key={API-KEY}&q=busan&aqi=no


// 응답
{
    "location": {
        "name": "Busan",
        "region": "Sindh",
        "country": "Pakistan",
        "lat": 27.92,
        "lon": 68.24,
        "tz_id": "Asia/Karachi",
        "localtime_epoch": 1669142949,
        "localtime": "2022-11-22 23:49"
    },
    ...
}



또는 실제 한국의 부산의 위도와 경도 데이터로 요청했지만 리스폰스는 서울로 나왔다.

// 요청 URL
http://api.weatherapi.com/v1/current.json?key={API-KEY}&q=35.1799528, 129.0752365&aqi=no

// 응답데이터
{
  "location": {
        "name": "Kumsadong",
        "region": "",
        "country": "South Korea",
        "lat": 35.18,
        "lon": 129.08,
        "tz_id": "Asia/Seoul",
        "localtime_epoch": 1669142398,
        "localtime": "2022-11-23 3:39"
    }, ...
}




Weather API는 클라이언트의 IP주소로도, 클라이언트가 위치한 지역의 날씨정보를 구할 수 있다.



그러면 어떻게 클라이언트의 IP주소를 얻을 수 있을까?
답은 가까이있다! 요청파라미터 @Req 에서 얻을 수 있다 :)

import {
  Controller,
  Get,
  Req,
} from '@nestjs/common';
import { Request } from 'express';
import { PostsService } from './posts.service';


@Controller('posts')
export class PostsController {
  constructor(private readonly postsService: PostsService) {}

  /**
   * [GET] /api/posts/weather/today
   */
  @Get('weather/today')
  async showTodayWeather(@Req() request: Request) {
    console.log(request);
    return;
  }
}


request를 콘솔로 출력해보니 여러개의 데이터가 나왔고, 그중 IP 주소도 볼 수 있다.
그러나 로컬호스트에서 실행했을때는 클라이언트의 주소가 127.0.0.1 로 되어있다.
로컬호스트로 오픈api를 호출할 때는 아래와 같은 오류가 발생한다.



클라이언트의 주소 IP를 IPv4 주소로 나타내기
- nestJS 에서 요청자의 IPv6 주소형식을 기본으로 하고있다. 만일 IPv4 주소형식으로 나타내고 싶다면 main.ts를 변경해야한다.

// main.ts

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


async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  ...
  await app.listen(3000, '0.0.0.0'); // IPv4 형식으로 변경
}
bootstrap();



현재 클라이언트의 위도와 경도를 어떻게 구하지?

아직 배포를 하지 않았지만, 배포를 한다고 가정할 때 실제 사용자가 게시글을 올릴 당시의 날씨정보를 같이 등록하는 케이스 말고도
개발단계 때의 클라이언트의 주소가 localhost(127.0.0.1) 일때도 고려해볼 필요가 있다.
클라이언트의 위도와 경도를 구하는 API는 어느 오픈소스에도 제공해주지않았다.
구글링을 하다가 운좋게 발견했다!
navigator.geolocation.getCurrentPosition() 와 같은
Geolocation API 는 사용자 위치정보를 조회하도록 허용만 해준다면, 현재 클라이언트의 위치정보(위도,경도) 데이터를 얻을 수 있다!
만일 위치를 지원하지 않은 브라우저(internet explorer) 거나 위치정보를 허용하지 않은 경우에는 '서울' 지역의 위도와 경도값을 리턴하는 방안으로 설계를 기획했다.

런타임에서 실행해본 결과 'ReferenceError: navigator is not defined' 라는 에러가 떴다.
필자가 얼마나 무식했음을 보여주는 과정이다.
Geolocation API 는 자바스크립트가 사용자의 실제위치를 브라우저에게 요청한다.
즉 브라우저가 지원해주는 API이라서 Node.js에서는 사용할수 없는 API 이다!
[Geolocation MDN 공식문서 ]

Geolocation API - Web APIs | MDN

The Geolocation API allows the user to provide their location to web applications if they so desire. For privacy reasons, the user is asked for permission to report location information.

developer.mozilla.org



MaxMind에서 만든 GeoLite 데이터를 Node.js 환경에서 지원해주는 패키지 GeoIP-lite 를 사용해보자.
GeoIP는 IP주소를 지역정보로 전환해주는 오픈소스 라이브러리이다.
GeoIP-lite 에서는 IPv4 에서 지역정보를 지원해준다는 점 유의해야한다.
계속 업데이트가 될 수 있으므로 아래 다큐먼트를 참고하길 바란다.

GitHub - geoip-lite/node-geoip: Native NodeJS implementation of MaxMind's GeoIP API -- works in node 0.6.3 and above, ask me abo

Native NodeJS implementation of MaxMind's GeoIP API -- works in node 0.6.3 and above, ask me about other versions - GitHub - geoip-lite/node-geoip: Native NodeJS implementation of MaxMind's...

github.com


GeoIP-lite 설치하기

$ npm i geoip-lite


[ 필자가 적용한 코드 ]

import * as geoip from 'geoip-lite';

@Injectable()
export class PostsService {
  constructor(
    // axios 를 사용하기 위한 HttpService 를 주입
    private axiosHttpService: HttpService,

    // postRepository 주입
    private readonly postsRepository: PostsRepository,

    // configModule주입
    private readonly configService: ConfigService,
  ) {}
  
  ...
    
   private getLocationData(IPAddress: string) {
    let lat, lng;

    // IPAddress가 로컬호스트라면, 작성자의 집주소로 한다.
    if (IPAddress === '127.0.0.1') {
      lat = this.configService.get<number>('LAT');
      lng = this.configService.get<number>('LNG');

      return { lat, lng };
    }

    const geoInfo = geoip.lookup(IPAddress);
    lat = geoInfo.range[0];
    lng = geoInfo.range[1];

    return { lat, lng };
  }

}



RxJS의 Observable 형태의 값에서 실질적인 데이터 받아오기

- 디버깅결과 axios를 이용하여 OpenAPI를 호출한 날씨정보가 Observable 형태로 되어있다!
async-await 를 적용하지 않은 Promise 객체처럼 꽁꽁감싸져있는 형태이다!


axios 사용방법 참고블로그에서 알게됐는데, rxJS의 lastValueFrom() 함수를 이용하면 되는거였다!
lastValueFrom() 함수는 비동기 함수이므로 async-await 를 이용해야한다.

/**
   * axios를 이용하여, WeatherAPI 로부터 현재 날씨정보를 갖고온다.
   */
  async getWeatherData(IPAddress: string) {
    // IP 주소에 대응되는 위치를 구한다.
    const { lat, lng } = this.getLocationData(IPAddress);

    // const { lat, lng } = await this.getLocationData();
    const weather = this.axiosHttpService
      .get(
        `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lng}&appid=${this.configService.get<string>(
          'OPEN_WEATHER_API',
        )}`,
      )
      .pipe(
        map((res) => res.data?.weather),
        map((weather) => {
          // 현재 날씨 정보를 리턴
          return weather[0]?.main;
        }),
      )
      .pipe(
        catchError(() => {
          // 403 CustomError을 호출한다.
          throw new FailGetWeatherDataException();
        }),
      );

    const result = await lastValueFrom(weather);
    return result;
  }

(완성) 현재날씨 데이터 파싱하여 가져오기

게시글 등록에서 IP주소로 클라이언트가 위치한 날씨데이터를 구하기위해서 파라미터 IPAddress 를 추가했다.
또한 엔티티에서도 날짜정보인 weather 필드를 추가했다. 전체코드는 바로 밑에 있는 래포지토리 링크를 참고하면 된다.

[필자의 깃헙 래포지토리]
- 깃헙래포를 보고 도움이 되셨다면 ⭐️star⭐️ 를 주시면 감사하겠습니다

GitHub - loveAlakazam/3_Posts

Contribute to loveAlakazam/3_Posts development by creating an account on GitHub.

github.com



(1) posts.service.ts

// 최종코드 : posts.service.ts

import { catchError, lastValueFrom, map, Observable } from 'rxjs';
import * as geoip from 'geoip-lite';

@Injectable()
export class PostsService {
  constructor(
    // axios 를 사용하기 위한 HttpService 를 주입
    private axiosHttpService: HttpService,

    // postRepository 주입
    private readonly postsRepository: PostsRepository,

    // configModule주입
    private readonly configService: ConfigService,
  ) {}

  /* 현재 IP주소로 위치정보 구하기 */
  private getLocationData(IPAddress: string) {
    let lat, lng;

    // IPAddress가 로컬호스트라면, 작성자의 집주소로 한다.
    if (IPAddress === '127.0.0.1') {
      lat = this.configService.get<number>('LAT');
      lng = this.configService.get<number>('LNG');

      return { lat, lng };
    }

    const geoInfo = geoip.lookup(IPAddress);
    lat = geoInfo.range[0];
    lng = geoInfo.range[1];

    return { lat, lng };
  }


  /* 날씨 데이터 구하기 */
  async getWeatherData(IPAddress: string) {
    // IP 주소에 대응되는 위치를 구한다.
    const { lat, lng } = this.getLocationData(IPAddress);

    // const { lat, lng } = await this.getLocationData();
    const weather = this.axiosHttpService
      .get(
        `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lng}&appid=${this.configService.get<string>(
          'OPEN_WEATHER_API',
        )}`,
      )
      .pipe(
        map((res) => res.data?.weather),
        map((weather) => {
          // 현재 날씨 정보를 리턴
          return weather[0]?.main;
        }),
      )
      .pipe(
        catchError(() => {
          // 403 CustomError을 호출한다.
          throw new FailGetWeatherDataException();
        }),
      );

    const result = await lastValueFrom(weather);
    return result;
  }
  
  
  /* 게시글 등록 */
  async createPost(
    user: Users,
    IPAddress: string,
    createPostDto: CreatePostDto,
  ): Promise<InsertResult> {
    try {
      if (createPostDto.postType === PostType.PRIVATE_POST) {
        const originPassword = createPostDto.postPassword;
        if (!originPassword) {
          throw new EmptyPostPasswordException();
        }

        // 비밀번호 정규표현식
        const isRegex = originPassword.match(PRIVATE_PASSWORD_REGEX);
        if (!isRegex) throw new InvalidPostPasswordRegexException();

        //입력받은 비밀번호를 암호화
        const hashedPostPassword = await this.hash(originPassword);
        createPostDto.postPassword = hashedPostPassword;
      }

      // 클라이언트 IP주소에 해당하는 위치를 구한뒤, 해당위치에서의 날씨정보를 구한다.
      const weatherInfo = await this.getWeatherData(IPAddress);

      // Post 추가
      return await this.postsRepository.createPost(
        user,
        createPostDto,
        weatherInfo,
      );
    } catch (error) {
      throw error;
    }
  }
  
  ...
}



(2) Insomnia 로 테스트 해본 결과 이다.
- 오늘날씨 조회
[GET] /api/posts/weather/today 는 날씨 데이터가 잘 나오는지
OpenAPI 와 통신이 잘되는지를 확인하기 위해서 만든 API 이다... ㅎㅎ;


- 공개글 등록


- 공개글 등록 후 조회 (날씨 데이터 추가 이전의 게시글들은 모두 weather 값이 null 이다)


- 비밀글 등록


- 비밀글 등록 이후 조회


(부록) Open Cage Geocoding API 를 활용하여, 위도/경도/주소 정보를 얻자.

현위치의 위도/경도 정보를 얻을 수 있는 API는 정말 많다. 구글링하다가 OpenCage 를 사용해봤다.
참고로 응답데이터를 나타낼때 언어도 선택할 수 있다! 또한 무료(trial)은 하루에 2500번을 호출할 수 있다.

OpenCage Geocoding API Documentation

Full documentation and reference for the OpenCage Geocoding API for forward and reverse geocoding: formats, parameters, response codes, best practices, etc.

opencagedata.com



직접 위도와 경도 데이터 입력하거나, 주소데이터를 입력해야만 리스폰스 데이터를 나온다.

  • 주소나 지역을 입력데이터로 할 때
https://api.opencagedata.com/geocode/v1/json?q=korea,+seoul&key={API-KEY}&language=ko&pretty=1
  • 위도와 경도를 입력데이터로 할 때
https://api.opencagedata.com/geocode/v1/json?q=37.5545547,+126.9707793&key={API-KEY}&language=ko&pretty=1


느낀점

  • 이번에는 다양한 오픈 API를 활용해서 내가 원하는 데이터를 얻는 방법을 스스로 터득했다.
  • Geolocation API 는 브라우저에서만 가능하다. 
  • Geolocation을 사용하지 못하는 상황에서 현재 클라이언트의 IP주소로 클라이언트의 현위치를 구하는 방법에 대해서 고민을 해봤다
    • 코드가 많이 부족하지만, 브라우저의 Geolocation기능이 좋긴하다.
    • 다만 클라이언트의 IP주소가 로컬호스트일 때 현재위치를 구하기가 어렵다.
  • axios가 어떤 용도에 쓰이는지 알게됐다.
    • 다음에 OpenAPI를 불러올 일이 생긴다면, axios 공식문서만으로도 Promise기반으로 나타내보자.
  • RxJS 의 Observable 에 대해서 좀더 알아볼 필요가 있다.
  • 직접 부딪혀보면서 고민한 과정을 솔직하게 담은 글을 포기하지 않고 끝까지 포스팅을 작성한 내자신이 자랑스럽다 :)



[공식다큐먼트]

Documentation | NestJS - A progressive Node.js framework

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Progamming), FP (Functional Programming), and FRP (Functional Reac

docs.nestjs.com


[참고자료]

더보기

[ Axios 정의 ]

[AXIOS] 📚 axios 설치 & 특징 & 문법 💯 정리

Axios 라이브러리 Axios는 브라우저, Node.js를 위한 Promise API를 활용하는 HTTP 비동기 통신 라이브러리 아다. 쉽게 말해서 백엔드랑 프론트엔드랑 통신을 쉽게하기 위해 Ajax와 더불어 사용한다. 이미

inpa.tistory.com

[ NestJS에서 axios 를 사용하여 외부 API 로부터 원하는 데이터를 추출해보자 ]

How to use Axios in NestJs - Code with Vlad

Learn how to use NestJs with Axios and the HTTP Module (@nestjs/axios). A richly featured HTTP client package

www.codewithvlad.com

[ NestJS에서는 클라이언트의 IP주소를 어디서 얻을 수 있을까? ]

[Nest Js] Nest Js 공식 문서 파헤치기 - OverView 2

트리스티가 Nest Js를 공부하며 남긴 기록입니다. 틀린 내용은 언제든지 말씀해주세요 ~! 📣 Controller란 무엇인가? Controller란 사용자(client)의 요청(request)을 처리하고, 응답(response)을 반환하는 역할

tristy.tistory.com

[ geolocation 활용하여 현재 클라이언트의 위도와 경도 정보를 얻기 ]

Navigator.geolocation - Web APIs | MDN

The Navigator.geolocation read-only property returns a Geolocation object that gives Web content access to the location of the device. This allows a Web site or app to offer customized results based on the user's location.

developer.mozilla.org

[JS] 📚 Geolocation API로 🗺️ 위도, 경도 얻고 ⛅ 날씨, 온도 정보를 얻어오기

HTML5 API Geolocation Geolocation API는 자바스크립트 프로그램이 사용자의 실제 위치를 브라우저에게 요청할 수 있도록 해준다. 물론 이런 정보들은 주요한 개인 정보와 관련되어 있으므로, 브라우저에

inpa.tistory.com

[Geolocation API가 Node.js에서는 지원할 수 없는 이유]

How can I use geolocation in my node.js to get user device lat/long

Good day all, pardon my question logic as I am new here. I am building my first fullstack App using React and Node.I am thinking of three approaches but not sure what will work best. APPROACH ONE I

stackoverflow.com

[ IP주소로 위치정보를 얻기 ]

Best node.js module for finding location?

I had found couple of node.js modules for finding the information about client location and network using ip address. Requirements: Location - country, city, state, latitude, longitude etc. Netwo...

stackoverflow.com

728x90
반응형
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함