티스토리 뷰
이미 수행기간이 끝난 프로젝트이지만, 선택항목 중 하나이자, 수행기간 때는 못했던 기능을 도전해보려고 한다.
외부 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 공식 문서 ]
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 보다는... 다른 날씨 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 공식문서 ]
MaxMind에서 만든 GeoLite 데이터를 Node.js 환경에서 지원해주는 패키지 GeoIP-lite 를 사용해보자.
GeoIP는 IP주소를 지역정보로 전환해주는 오픈소스 라이브러리이다.
GeoIP-lite 에서는 IPv4 에서 지역정보를 지원해준다는 점 유의해야한다.
계속 업데이트가 될 수 있으므로 아래 다큐먼트를 참고하길 바란다.
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⭐️ 를 주시면 감사하겠습니다
(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번을 호출할 수 있다.
직접 위도와 경도 데이터 입력하거나, 주소데이터를 입력해야만 리스폰스 데이터를 나온다.
- 주소나 지역을 입력데이터로 할 때
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 에 대해서 좀더 알아볼 필요가 있다.
- 직접 부딪혀보면서 고민한 과정을 솔직하게 담은 글을 포기하지 않고 끝까지 포스팅을 작성한 내자신이 자랑스럽다 :)
[공식다큐먼트]
[참고자료]
[ Axios 정의 ]
[ NestJS에서 axios 를 사용하여 외부 API 로부터 원하는 데이터를 추출해보자 ]
[ NestJS에서는 클라이언트의 IP주소를 어디서 얻을 수 있을까? ]
[ geolocation 활용하여 현재 클라이언트의 위도와 경도 정보를 얻기 ]
[Geolocation API가 Node.js에서는 지원할 수 없는 이유]
[ IP주소로 위치정보를 얻기 ]
'Backend > 꾸준히 TIL' 카테고리의 다른 글
[NestJS+OAuth2.0] OAuth2.0 연동로그인 구현하기 (0) | 2023.01.15 |
---|---|
계층형 아키텍쳐(Layered Architectures) (0) | 2023.01.08 |
[NestJS+Mongoose] MongoDB aggregation 활용하기 (0) | 2022.11.21 |
[NestJS+MongoDB+Mongoose] MongoDB 외래키 나타내기 (0) | 2022.11.20 |
[NestJS] command not found: nest (0) | 2022.11.17 |
- Total
- Today
- Yesterday
- gem
- IT용어
- 습관개선
- Jekyll
- 한달어스
- vscode
- typeORM
- 클린아키텍쳐
- 나도 할 수 있다
- git
- 미완
- 참고
- OS
- 스마트폰중독
- 갓생살자
- Mongoose
- nestjs
- Nest.js
- jest
- node.js
- TDD
- MongoDB
- 한달독서
- MySQL
- nestjs jest
- RDBMS
- 디지털디톡스
- 바이트디그리
- 개발용어
- TypeScript
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |