Introduction
My original idea for this project was for it to be a repository
Typewolf for music genres. In other words, just as
designers go on Typewolf to find websites that have used a certain font, music
enthusiasts should be able to go on The Genre Isn't World (TGIW) to find songs
classified under any specific genre.
The curation model used by Typewolf (and several web inspiration sites) has also been adopted for the project. Newly curated music is added by a group of authorised users known as contributors. Virtually all the features of these curated songs are retrieved automatically by the application's server (via the Spotify API).
The final feature of TGIW is the 'Genre Finder'—a tool with which (even) unauthenticated users can obtain the genres for any song they search for.
Purpose and Goal
Although music streaming services are especially optimised for music discovery, their 'genres' pages are usually (and probably deliberately) non-exhaustive. This is exemplified by the genres listed on Spotify's search screen shown below.

Additionally, 'World Music1'.—a catch-all term used by everyone from British music stores to the American National Academy of Recording Arts and Sciences—(reductively) lumps hundreds of genres into one. The primary output of this project is therefore intended to be a comprehensive catalog that distills these oft-condensed genre information into a friendly user interface2.
1. The pseudonymous 'World' genre inspired the project's name. ↩
2. Glenn Macdonald's seminal website provides an exhaustive list of genres too. Its UI is however quite avant garde. ↩
Preliminary Planning and Design
I started the project by hand-drawing low fidelity wireframes then fleshing them out into Figma prototype screens.


When I carry out personal projects like TGIW (or this portfolio site), I do not generate comprehensive prototypes with fully connected screens. Instead, I make the design somewhat sketchy and flexible enough to be improved on3. This 'design then make it better' approach enables me to refactor the UI whilst developing the frontend. The overall project design was largely inspired by Mobbin and Bonsound.
The Figma document containing the screens designed for this project is available here. Certain pages such as the 'List of Genres' page were eliminated in the final build in favor of a search feature that provides a better user experience.
3. Figma screens might not look as good in living browser documents. Therefore, these changes often involve optimising predefined designs for the browser. ↩
Features Spotlight
Authentication and Authorization
NestJS, a TypeScript-first Express.js wrapper was chosen as the tool for building the backend. I selected it because of its similarity to batteries-included, opinionated frameworks such as ASP.NET Core or Spring Boot. However, unlike those other frameworks, NestJS does not have a native auth module. As a result, I decided to use Firebase Auth to build TGIW's authentication system.
The first hurdle I faced whilst using Firebase Auth was in integrating its data (stored on remote Google servers) with my app's PostgreSQL database. To solve this issue, I used solutions suggested in a few Stack Overflow responses. Essentially, every unique Firebase UID is also used as a key to store extra data (such as a contributor's songs) in the SQL database. Authorization—the separation of contributors from regular users—was carried out using Firebase Auth's Custom User Claims feature. The snippet below provides a glimpse of how the app's authentication and authorization systems work. All other auth methods are available on GitHub.
async createUser(createUserDto: CreateUserDto) {
const { userName, role = 'user', ...restOfDto } = createUserDto;
// Ensure username is not already in use.
await this.validateUsername(userName);
try {
// Add user to Firebase.
const user = await firebaseAdmin.auth().createUser(restOfDto);
const { uid: id } = user;
// Set user's role to enable authorization.
await firebaseAdmin.auth().setCustomUserClaims(id, { role });
// Add user to database.
const dbUser = this.userRepository.create({ id, userName });
await this.userRepository.save(dbUser);
return { ...user, userName };
} catch (error) {
// Internal server error used because Firebase error codes are strings (and plentiful).
throw new InternalServerErrorException(error.errorInfo.message);
}
}
For the frontend, I decided to use Next.js in server-side rendering (SSR) mode. This decision was inspired by a (somewhat old) blog post by Colin McDonnell. Of all the reasons he provides for prioritizing SSR apps, I was particularly swayed by this one:
The user experience is unbeatable. With good caching and performant fetching, you can swap out the ubiquitous SPA spinner for snappy page loads.
I found that his comments held true and also, the SSR approach "eliminates a flash of unauthenticated content" as stated in the Next.js docs. This latter reason was perhaps my biggest reason for using SSR in all authenticated TGIW pages.
Pagination and Infinite Scroll
To present all songs that have been added to TGIW, I considered two options for data fetching: pagination and virtualization (with a library like react-virtualised). I settled for pagination to avoid the eventual drawback of large payloads that feed virtualized lists. The snippet below shows the method that is used to paginate server responses.
paginateResponse<DataType>(
data: DataType,
page: number,
take: number,
fullDataLength: number,
) {
const totalPages = Math.ceil(fullDataLength / take);
const nextPage = page + 1 > totalPages ? null : page + 1;
const prevPage = page - 1 < 1 ? null : page - 1;
return {
data,
count: fullDataLength,
currentPage: page,
nextPage,
prevPage,
totalPages,
};
}
The method is fed with data that has already been filtered with TypeORM's skip
and take
methods.
On the frontend, I decided that the pagination will be implemented with an
infinite scroll feature similar to the approach used in the Mobbin app (shown
below).
Two React hooks form the backbone of the infinite scroll (as used here): useInfiniteQuery for handling server cache state and useInfiniteScroll which is a pleasantly designed wrapper around the Intersection Observer API.
Colors Galore
Music apps (such as Spotify) and websites (such as Bonsound) often extract prominent image colors for use in their UIs. I initially decided to do the same thing for TGIW using a client-side library that I discovered in a Paystack blog post. However, I eventually decided that (for performance and a reduced client bundle), such color manipulations should be carried out on the server.

Two color libraries were adopted to carry out these server-side color manipulations: node-vibrant and chroma.js. The former grabs prominent colors from images whilst the latter carries out general manipulation (lightening, darkening, random colors etc.). The project's final color manipulation methods are available here and the image above illustrates how they were applied for a specific song. Similar approaches were used to generate artist and genre colors as well.
Search
I adore the delightful rapidity of Spotify's search experience (shown below). Consequently, I decided to build a similar feature in TGIW. The search is entirely powered by server-side filters used to fetch data from the database. Only one endpoint (to fetch songs with joined genres and artists data) is called as the user enters a query and each request is debounced on the client to improve performance. The genre finder operates in a similar manner to the search feature.
Song Features Data Visualization
Certain song features are visualised with a custom component. This component is a very minimal version of the visualizations in this RapidAPI year-end review.


As shown below, the component was built with regular HTML (JSX) elements in lieu of a large-ish vizualization library such as D3.js.
<>
<div aria-hidden className="flex h-11 items-center justify-between pt-4">
<p className="text-left text-sm capitalize text-tgiwPurplish text-opacity-70">
{featureName}
</p>
<div className="relative ml-10 flex h-5 w-3/5 shrink-0 items-center rounded-md bg-tgiwBlue px-2">
<div className="percentage-gauge absolute flex h-3 w-4 justify-center rounded-full bg-tgiwOrange">
<span className="-translate-y-6 text-center text-xs text-tgiwPurplish text-opacity-70">
{roundedValue}%
</span>
</div>
</div>
</div>
// Visually hidden text to make the component more accessible.
<VisuallyHidden>
{featureName} is {roundedValue}%
</VisuallyHidden>
// Next.js's Styled JSX is used to add readable dynamic styles.
<style jsx>
{`
.percentage-gauge {
left: ${`${roundedValue}%`};
background-color: ${getGaugeColor(roundedValue)};
}
`}
</style>
</>
To improve the component's accessibility, a visually hidden component containing clear textual information is provided for users who use screen readers. The actual visualization is only available to sighted users.
Future Improvements
- More motion: While CSS animations and transitions have been used heavily in this project, I believe that adding more tasteful animations (perhaps spring physics based ones) will add more zhuzh to the app's UX.
- Deepen Spotify integration: A 'login in with Spotify' feature will make it possible to allow users to add music to their (Spotify) libraries or playlists directly from TGIW.
- Allow contributions from all users: A rich content platform is eventually going to need contributions from lots of people. Therefore, I think a system by which regular users could add songs will be a great addition. Authorised contributors could act as gatekeepers who vet such songs. Besides, the idea of users from all parts of the globe contributing their favorite local music aligns with the crux of the inspiration behind 'The Genre Isn't World'.