Tiny Projects: Currency Conversion Table

@lloydjatkinson
Pretty neat
Pretty neat

This is the first post in my new series of tiny projects. Unlike my main project page, these will be small, quick, and simple projects that I can complete in a day or two. I took inspiration from another “tiny projects” post I saw on Julia Evans’ site and thought it would be fun to document some of the small random projects I do occasionally too.

The source code is available on GitHub and the site is available here.

My and my partner have both needed to lookup varying amounts of money between pounds and złoty recently, so I decided to make a simple table to do this. I might add the ability to type in a value and have it update the table, but for now, it’s just a static table.

I’m using a free exchange rate API and I don’t want to make too many requests. This works well with having a static table; the values are looked up once at build time and then the table is generated. I use a scheduled GitHub action to build the site every 6 hours.

I used the Astro framework to build the site (the same framework I use to build my site that you are reading now). Additionally, I used TypeScript for the logic and Tailwind for the styling.

In total it took about two or three hours to build, with a few gaps in between, according to the Git history. In fact a good amount of the time was spent trying to decide how I wanted the design to look.

The main areas of interest in the code include the API request, the calculations using the fetched exchange rate and the currency formatting. Here’s the code for the API request:

export type Query = Readonly<{
    amount: number;
    from: string;
    to: string;
}>;

export type Info = Readonly<{
    rate: number;
    timestamp: number;
}>;

export type CurrencyResponse = Readonly<{
    date: Date;
    info: Info;
    query: Query;
    result: number;
    success: boolean;
}>;

export const fetchExchangeRate = async (from: string, to: string): Promise<CurrencyResponse> => {
    const response = await fetch(`https://api.exchangerate.host/convert?to=${to}&from=${from}`, {
        redirect: 'follow'
    });
    
    return await response.json() as CurrencyResponse;
};

Here is the code for the calculations. The (made up) scale is just a series of convenient and common values. One of the points of this project was to make it easy to lookup common amounts.

const { result } = await fetchExchangeRate('GBP', 'PLN');

const scale = [
	1,
	10,
	50,
	100,
	200,
	500,
	1000,
	2000,
	5000,
	10000,
	15000,
];

const gbp = scale.map((value) => {
	return {
		value,
		converted: value * result,
	};
});

const pln = scale.map((value) => {
	return {
		value,
		converted: value / result,
	};
});

The following component is where the currency formatting happens. I use the Intl.NumberFormat API to format the numbers. It’s a nice change for such a real-world use-case to be supported natively by Node/browser.

---
export type Props = {
    readonly locale: 'en-GB' | 'pl-PL';
    readonly symbol: 'GBP' | 'PLN';
    readonly value: number;
};

const { locale, symbol, value } = Astro.props as Props;

const formatted = new Intl.NumberFormat(locale, {
    style: 'currency',
    currency: symbol,
}).format(value);
---
<span>{ formatted }</span>

Here’s how the currency component is used. Straightforward and easy to understand.

<div class="flex flex-col gap-4">
    {
        pln.map((item) => (
            <div class="grid grid-cols-2">
                <Currency locale="pl-PL" symbol="PLN" value={ item.value } />
                <Currency locale="en-GB" symbol="GBP" value={ item.converted } />
            </div>
        ))
    }
</div>

There are a couple of features missing from this codebase that I would consider standard in any normal project.

  • I don’t have any exception handling. If there’s a fault with the API the site won’t be deployed. I could add logic to deal with this, but this project is fairly low risk. The next deploy will happen six hours later and the third-party API will probably be back up by then. In the mean time the site will simply be six hours out of date.

  • I would implement this exception handling with a Result type from the functional programming world. I won’t go into details on what that is here but know that I am a big fan of this pattern. Read about it here.

  • There’s no caching or any abstraction between the API and the site. Normally I’d setup an API that acts as a proxy to the third-party API. This would allow me to cache the results and also to add some additional logic. But again, this project is fairly low risk and simple.

Overall, I’m happy with how this turned out. It’s a simple project that the two of us (and maybe relatives too) can use and it was fun to build. I’ll be doing more of these in the future. Thanks for reading my first post in the “Tiny Projects” series!

Share:

Need help with your software project? Let's talk

Stay up to date

Subscribe to my newsletter to stay up to date on my articles and projects

Support me on Kofi