Adding Data and Types

Now we have an overengineered "Hello world", let's make it actually do something.

I've gone back and forth on calling types of Pokemon (as in Pikachu and Squirtle and their unique data) Breeds and Species. I'm going with Species this time, but you do you!

Let's create a /data/species directory in our root and add 4 .json files as such, so our project now looks like this

.git/
src/
  - index.ts
data/
  species/      <-- the directory we just added
    - 1.json    <-- empty file, fill with bulbasaur's data
    - 4.json    <-- empty file, fill with charmander's data
    - 7.json    <-- empty file, fill with squirtle's data
    - 25.json   <-- empty file, fill with pikachu's data
package.json
tsconfig.json
yarn.lock

In the files, let's put this data (the id of the pokemon lines up with the filename):

in 1.json

{
  "id": 1,
  "name": "Bulbasaur",
  "types": [
    "Grass",
    "Poison"
  ],
  "stats": {
    "hp": 45,
    "attack": 49,
    "defense": 49,
    "spAttack": 65,
    "spDefence": 65,
    "speed": 45
  }
}

in 4.json

{
  "id": 4,
  "name": "Charmander",
  "types": [
    "Fire"
  ],
  "stats": {
    "hp": 39,
    "attack": 52,
    "defense": 43,
    "spAttack": 60,
    "spDefense": 50,
    "speed": 65
  }
}

in 7.json

{
  "id": 7,
  "name": "Squirtle",
  "types": [
    "Water"
  ],
  "stats": {
    "hp": 44,
    "attack": 48,
    "defense": 65,
    "sp-atk": 50,
    "sp-def": 64,
    "speed": 43
  }
}

in 25.json

{
  "id": 25,
  "name": "Pikachu",
  "types": [
    "Electric"
  ],
  "stats": {
    "hp": 35,
    "attack": 55,
    "defense": 40,
    "sp-atk": 50,
    "sp-def": 50,
    "speed": 90
  }
}

Whew! We good? K. If not, lemme know!

Let's Start Reading Files!

Let's create a new directory called species-service and toss in a file.ts so our project now looks like this:

.git/
src/
  - index.ts
  species-service/
    - file.ts
data/
  species/      
    - 1.json
    - 4.json
    - 7.json
    - 25.json
package.json
tsconfig.json
yarn.lock

We're going to have a few different iterations ofspecies-services so this file.ts is the one that will interact with files. You can go all the way with this file approach, or fork off and do the database approach as we'll do in future chapters, as you can do it either way.

Since we're going to be using files, we need to import Node.js's File Service module, fs.

And since that file is going to be at a specific path, let's also import path.

And then let's add and immediately export a getSpecies function that takes in a species ID and returns some data.

import fs from 'fs';
import path from 'path';

export const getSpecies(id: number) : Promise<any> => {
  // todo: read the file
  // todo: parse the file
  // todo: handle errors if there are any 
}

Sweet! This is a solid starting place.

I'm returning a Promise, because I know that our other approaches to storing and retrieving data, and although our code with being synchronous, we want to make them as similar as possible, as we're going to be doing this in a few different ways. A peek into what we'll be doing is called Dependency Injection, and it requires that your different services (like one that could read from files, a database, or API) have the same interface. You'll see what/why/how later!

I don't like returning an any. One of the benefits of Typescript is it keeps you honest. Let's create a types directory and add our types to it.

There are so many cool things about Typescript, and one of them is to add a types.d.ts that encapsulates all your types. I'm doing it more explicitly here because I prefer this experience, but I'll add resources for if you want to go that other direction.

Let's add a types directory to our src, and add an index.ts where we'll put our types.

.git/
src/
  - index.ts
  species-service/
    - file.ts
  /types        <-- added this directory
    - index.ts  <-- added this empty file
data/
  species/      
    - 1.json
    - 4.json
    - 7.json
    - 25.json
package.json
tsconfig.json
yarn.lock

In that index.ts we just added, throw these types in there (you can leave out the comments)

export interface StatGroup {
  hp: number;        // the base HP of the species, and resulting Pokemon 
  attack: number;    // the base Attack of the species, and resulting Pokemon 
  defense: number;   // the base Defense of the species, and resulting Pokemon 
  spAttack: number;  // the base Special Attack of the species, and resulting Pokemon 
  spDefense: number; // the base Special Defense of the species, and resulting Pokemon
  speed: number;     // the base Speed of the species, and resulting Pokemon
}

export interface Species {
  id: number;
  name: string;     // the display name of the pokemon
  types: string[];  // fire, water, grass, etc
  stats: StatGroup; // that type we created above
}

We'll be using StatGroup in a few places, so it made sense to make a new type for it.

Back to our ./species-service/file.ts, let's continue filling out our getSpecies function!

We'll add an import for these new types, and make it so we're returning that type!

import fs from 'fs';
import path from 'path';
import { Species } from '../types';

export const getSpecies = (id: number): Promise<Species> => {
  
}

You should be getting an error saying that if you have a Promise of a specific type, you need to return that. Let's address that in a sec.

For now, let's resolve with an empty value to satisfy the linter, and then fill out our function step by step:

import fs from 'fs';
import path from 'path';
import { Species } from '../types';

export const getSpecies = (_id: number): Promise<Species> => {
  return new Promise((resolve, _reject) => {
    resolve({} as Species)
  })
}

This is a unique way of handling Promise functions. I learned it from my buddy Tyler, and have stuck with it! You can create a Promise, which takes in a function that has the resolve and the reject function. You call these functions to surface successful and failure cases. If you call the resolve function, that will pass data to the .then() function. If you call the reject function, that will pass data to the .catch() statement. We'll use both on this route!

Last updated