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
runstslint
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 thedocs
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!
Top comments (1)
Interesting!