Getting started - configuration

Our application fully works now. But we can improve it putting the hard-coded URL as configuration. Let's do that.

Application configuration

We have to define a configuration object that will hold the URL of the quote endpoint. Replace the src/index.ts file content with the following:

import { start } from "seqflow-js";
import { Main } from "./Main";
import "./index.css";

start(document.getElementById("root"), Main, undefined, {
	log: {
		info: (l: Log) => console.info(l),
		error: (l: Log) => console.error(l),
		debug: (l: Log) => console.debug(l),
	},
	// The configuration object
	config: {
		api: {
			// The URL of the quote endpoint
			baseUrl: "https://api.quotable.io",
		},
	}
});

// This is required to make typescript happy
declare module "seqflow-js" {
	interface ApplicationConfiguration {
		api: {
			baseUrl: string;
		};
	}
}

We defined a configuration object that holds the URL of the quote endpoint. We passed this object to the start function as the fourth argument.

Let's see how we can use this configuration object. Replace the src/Main.tsx file content with the following:

import { SeqflowFunctionContext } from "seqflow-js";

interface Quote {
	author: string;
	content: string;
}

// Use the `baseUrl`
async function getRandomQuote(baseUrl: string): Promise<Quote> {
	const res = await fetch(`${baseUrl}/random`)
	return await res.json();
}

async function Quote(this: SeqflowFunctionContext, { quote }: { quote: Quote }) {
	this.renderSync(
		<>
			<div>{quote.content}</div>
			<div>{quote.author}</div>
		</>
	);
}

async function Loading(this: SeqflowFunctionContext) {
	this.renderSync(
		<p>Loading...</p>
	);
}
async function ErrorMessage(this: SeqflowFunctionContext, data: { message: string }) {
	this.renderSync(
		<p>{data.message}</p>
	);
}
async function Spot(this: SeqflowFunctionContext) {}

export async function Main(this: SeqflowFunctionContext) {
	const fetchAndRender = async () => {
		this.replaceChild("quote", () => <Loading key="quote" />);

		let quote: Quote;
		try {
			// this.app.config is the configuration object
			quote = await getRandomQuote(this.app.config.api.baseUrl);
		} catch (error) {
			this.replaceChild("quote", () => <ErrorMessage key="quote" message={error.message} />);
			return;
		}
		this.replaceChild("quote", () => <Quote key="quote" quote={quote} />);
	}

	const button = <button type='button'>Refresh</button> as HTMLButtonElement;
	this.renderSync(
		<>
			{button}
			<Spot key="quote" />
		</>
	);

	await fetchAndRender();

	const events = this.waitEvents(
		this.domEvent('click', { el: button })
	)
	for await (const _ of events) {
		button.disabled = true;
		await fetchAndRender();
		button.disabled = false;
	}
}

We changed the getRandomQuote function to receive the baseUrl as an argument. We used the this.app.config object to access the configuration object.

Conclusion

Even if the configuration part is not commonly covered in tutorials, it's an essential part of any application. In this guide, we learned how to define a configuration object and how to use it in our application.