DEV Community

Cover image for A weather API with Node.js, OvernightJS and TypeScript
Francesco Napoletano
Francesco Napoletano

Posted on

A weather API with Node.js, OvernightJS and TypeScript

I am, like many of us, working from home. I leave my house only for basic necessities, like food or emergencies. My time is split between work, my wife, videogames and some side-projects.

While cleaning up my GitHub account I've found this little project I made a while ago: a weather API with Node.js, OvernightJS and TypeScript. I was pretty proud of it (it's 100% TypeScript, there's automatic documentation, linting and tests), so I said: "Let's write a post about it".

My goal was to map the OpenWeatherMap API to serve a subset of its data in our app, while learning OvernightJS.

What is OvernightJS?

OvernightJS is a simple library to add TypeScript decorators to Express routes.

The thing I like the most about OvernightJS is its simplicity: it's not meant to act as an extra layer on top of Express or take you away from the RESTful style of writing back-end web APIs.

Check this tutorial if you want to get started with OvernightJS.

Let's start

Ok, what will this project do? The app will expose 3 endpoints for 3 specific cities: Stockholm, Madrid and Milan.

ENDPOINTS
http://localhost:4000/api/weather/CITY/forecast Forecast for CITY (stockholm,madrid,amsterdam)
http://localhost:4000/api/weather/CITY/current Current weather for CITY (stockholm,madrid,amsterdam)
http://localhost:4000/api/weather/stats Avg. temp for the 3 cities and the highest temp/humidity

Application setup

First of all: for safety reasons, the API token is not included in this repo. Please check .env.example for details. Get an API key from https://openweathermap.org/api and store it in a file called .env file at the root of your project.

# The port where the application is listening
APPLICATION_PORT=4000

# The https://openweathermap.org/ API token
OWM_APP_ID=YOUR_API_TOKEN

Now, let's setup our app. We're adding a bit of stuff here:

  • Application scripts in package.json
  • tsconfig.json for TypeScript
  • tslint.json for linting

Just run:

npm install -g typescript
tsc --init

You should find your TypeScript config file in the root of your project. If you want to follow a more detailed guide check https://medium.com/free-code-camp/how-to-set-up-a-typescript-project-67b427114884.

You can just grab the three files mentioned above from the github repo if you're lazy.

Let's dive a bit into these files

package.json

Apart from the packages (you can install them by running an npm install once the file is in your project directory) the interesting part here is the scripts section.

"scripts": {
    "docs": "./node_modules/.bin/typedoc --out docs --mode modules src",
    "start-dev": "nodemon --config \"./util/nodemon.json\"/",
    "build": "rm -rf ./dist/ && tsc",
    "start": "node dist/start.js",
    "test": "./node_modules/.bin/mocha -r ts-node/register src/**/*.spec.ts",
    "lint": "tslint --fix -c tslint.json 'src/**/*.ts'"
},

The scripts are pretty self-explanatory:

  • docs generates the app documentation using TypeDoc
  • start-dev launches the app in "watch-mode" for your local environment
  • build compiles the code for distribution
  • start launches the app
  • test runs the tests for your app
  • lint runs tslint for your code

tsconfig.json & tslint.json

Configuration file for TypeScript and linting rules. Not much to say, pretty standard files...

{
    "compilerOptions": {
        "module": "commonjs",
        "esModuleInterop": true,
        "strict": true,
        "baseUrl": "./",
        "outDir": "dist",
        "removeComments": true,
        "experimentalDecorators": true,
        "target": "es6",
        "emitDecoratorMetadata": true,
        "moduleResolution": "node",
        "importHelpers": true,
        "types": [
            "node"
        ],
        "typeRoots": [
            "node_modules/@types"
        ]
    },
    "typedocOptions": {
        "mode": "modules",
        "out": "docs"
    },
    "include": [
        "./src/**/*.ts"
    ]
}

And...

{
    "extends": "tslint:recommended",
    "rules": {
        "max-line-length": {
            "options": [100]
        },
        "member-ordering": false,
        "no-consecutive-blank-lines": false,
        "object-literal-sort-keys": false,
        "ordered-imports": false,
        "quotemark": [true, "single"],
        "variable-name": [true, "allow-leading-underscore"]
    }
}

Let's move to our app...

The server

Our app entry point will be src/start.ts as you can see from util/nodemon.json (check the start-dev in the scripts section of our package.json.

The script simply includes our ApiServer class which will setup our controllers on the routes configured using OvernightJS. OvernightJS makes this super simple, just a loop on the controllers.

/**
 * Adds controllers to the application
 * by looping on the imported classes
 */
private setupControllers(): void {
    const ctlrInstances = [];
    for (const name in controllers) {
        if (controllers.hasOwnProperty(name)) {
            const controller = (controllers as any)[name];
            ctlrInstances.push(new controller());
        }
    }
    super.addControllers(ctlrInstances);
}  

The controllers

Let's see how OvernightJS makes easy for us to configure our application controllers: first of all let's define a class...

/**
 * @class ApiController
 * is the class managing endpoints for our API
 */
@Controller('api')
export class ApiController {

}

/api (check the @Controller annotation) will be the "root" of our URL. Each method of this class will have its own route...

/**
 * It should return the Current Weather Forecast given a city as input among three
 * @param req
 * @param res
 */
@Get('weather/:city/current')
@Middleware([cityValidatorMiddleware])
private async getCurrentWeather(req: Request, res: Response) {
    let weather;
    try {
        weather = await this.owm.getCurrentWeather(req.params.city);
        return res.status(Ok).json({
            currentWeather: weather,
        });
    } catch (err) {
        return res.status(InternalServerError).json({ error: err });
    }
}

We're adding @Get annotation to define a GET route with the weather/:city/current path and a @Middleware to validate our request (we only serve three cities, do you remember?).

The method itself is pretty simple: we call the getCurrentWeather() method in the src/openweathermap/OpenWeatherMapApiClient.ts class and return the result as a JSON object.

{
    "currentWeather": {
        "cityName": "Amsterdam",
        "cityWeather": "Clear, clear sky"
    }
}

The api will answer at the http://localhost:4000/api/weather/amsterdam/current.

Using OvernightJS will allow you to define your routes in a super easy way, and inside your controllers, closer to your actual code. To me it's more clear than the classic "Express way":

// GET method route
app.get('/', function (req, res) {
    res.send('GET request to the homepage');
});

// POST method route
app.post('/', function (req, res) {
    res.send('POST request to the homepage');
});

Recap

Here's a little recap, useful if you just want to download and run the code:

  • The three allowed cities are Madrid, Stockholm, Amsterdam
  • Run tests with npm run test
  • The project is using OvernightJS, a simple library to add TypeScript decorators for methods meant to call Express routes. It also includes a package for printing logs.
  • You can generate TypeDoc documentation by running npm run docs, the documentation will be stored in the docs folder.
  • The project is using dotenv-safe for env. configuration. See .env.example for details.

Ok, we're done. Check the full code here: https://github.com/napolux/weather-api-typescript.

The code itself is over-commented, so you should not have any problem in following the code flow.

If you have any question just get in touch!

Oldest comments (0)