Defining environmental variables

By default, effect extracts Config values from the default environment. For node this is process.env.

All we need then is to define this configuration when running the app. Let's update the dev command inside package.json:

{
  "name": "effect-getting-started-course",
  "version": "1.0.0",
  "description": "",
  "main": "src/index.js",
  "scripts": {
    "dev": "BASE_URL=https://pokeapi.co tsx src/index.ts"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/node": "^20.14.10",
    "tsx": "^4.16.2",
    "typescript": "^5.5.3"
  },
  "dependencies": {
    "effect": "^3.10.0"
  }
}

Now we can execute pnpm run dev at the app works as expected:

> effect-getting-started-course@1.0.0 dev
> BASE_URL=https://pokeapi.co tsx src/index.ts

{ id: 445, order: 570, name: 'garchomp', height: 19, weight: 950 }

We definitely reached a solid point in our implementation. As a single program this works great.

import { Schema } from "effect";
import { Config, Data, Effect } from "effect";

/** Schema **/
class Pokemon extends Schema.Class<Pokemon>("Pokemon")({
  id: Schema.Number,
  order: Schema.Number,
  name: Schema.String,
  height: Schema.Number,
  weight: Schema.Number,
}) {}


/** Errors **/
class FetchError extends Data.TaggedError("FetchError")<{}> {}
class JsonError extends Data.TaggedError("JsonError")<{}> {}


/** Configuration **/
const config = Config.string("BASE_URL");


/** Implementation **/
const fetchRequest = (baseUrl: string) =>
  Effect.tryPromise({
    try: () => fetch(`${baseUrl}/api/v2/pokemon/garchomp/`),
    catch: () => new FetchError(),
  });

const jsonResponse = (response: Response) =>
  Effect.tryPromise({
    try: () => response.json(),
    catch: () => new JsonError(),
  });

const decodePokemon = Schema.decodeUnknown(Pokemon);

const program = Effect.gen(function* () {
  const baseUrl = yield* config;
  const response = yield* fetchRequest(baseUrl);
  if (!response.ok) {
    return yield* new FetchError();
  }

  const json = yield* jsonResponse(response);

  return yield* decodePokemon(json);
});


/** Error handling **/
const main = program.pipe(
  Effect.catchTags({
    FetchError: () => Effect.succeed("Fetch error"),
    JsonError: () => Effect.succeed("Json error"),
    ParseError: () => Effect.succeed("Parse error"),
  })
);


/** Running effect **/
Effect.runPromise(main).then(console.log);
Effect Playground

However, we are usually not building apps with single functions, right? We need a more powerful way to organize code and modules. What about testing also? How do we inject a mock implementation?

Brace yourself, we are about to move from "how to implement an effect function" to "how to use effect to organize a production app".

That's the real power that effect bestows on you, let's see how!