Search code examples
typescriptobservablenestjs

Return data from response within an observable in Nestjs


I'm new to Nestjs, Typescript and basically backend development. And I'm working on a simple Weather app, where I fetch weather data from Open Weather API. I'm using Nest built-in HttpModule which is wrapping Axios within, then using HttpService to make a GET request to Open weather. The request is returning an Observable which is totally news to me. How do I extract the actual response data from the observable in the Injectable service and return it to the Controller?

Here's my weather.service.ts

import { Injectable, HttpService } from '@nestjs/common';

@Injectable()
export class AppService {
  constructor(private httpService: HttpService) {}

  getWeather() {
    let obs = this.httpService.get('https://api.openweathermap.org/data/2.5/weather?q=cairo&appid=c9661625b3eb09eed099288fbfad560a');
    
    console.log('just before subscribe');
    
    obs.subscribe((x) => {
        let {weather} = x.data;
        console.log(weather);
    })
    console.log('After subscribe');
    
    // TODO: Should extract and return response data f
    // return;
  }
}

And this is weather.controller.ts

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getWeather() {
    const res = this.appService.getWeather();
    return res;
  }
}

Also can someone clarify what are the missing types in my code?


Solution

  • RxJS Observables are essentially advanced callbacks. Because they work in an asynchronous fashion, you need to have your code deal with that. Nest can handle if an Observable is returned from the controller and will subscribe to it under the hood for you, so all you should need to do in your service is something like this:

    import { Injectable, HttpService } from '@nestjs/common';
    
    @Injectable()
    export class AppService {
      constructor(private httpService: HttpService) {}
    
      getWeather() {
        return this.httpService.get('https://api.openweathermap.org/data/2.5/weather?q=cairo&appid=c9661625b3eb09eed099288fbfad560a').pipe(
          map(response => response.data)
        );
       
      }
    }
    

    The map is imported from rxjs/operators and is similar to Array.prototype.map in that it can take values in and transform them as necessary. From here, your Controller just needs to return this.appService.getWeather(), and Nest will handle the rest.

    The other option you have is to convert the observable to a promise using .toPromise() and then you can use your usual async/await syntax, which is another valid choice.


    RxJS v7 and on

    toPromise() was deprecated in RxJS v7. It's now recommended to use lastValueFrom(observable) or firstValueFrom(observable) instead to make an observable async.