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);
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!