Skip to content

ivandotv/live-radio

Repository files navigation

Online Radio Player

Progressive web application for listening to online radio.

Motivation

The application started as a side project during the covid pandemic. I wanted to have a simple application to listen to online radio, that is not overrun with ads and works both on desktop and on mobile.

Then later, it became a test bed for experimenting with different ways to structure a Next.js application (more on that later).

The setup

The application is built with the Next.js framework. It is installable as a PWA, and it can be used anonymously (data is stored in IndexedDB) or as a registered user (data is stored in MongoDB Atlas).

You can seamlessly switch from anonymous to a registered user, and import the data from the anonymous account.

Application also supports multiple languages via Next.js internationalized routing and LinguiJS internationalization library.

Radio station data is consumed via free and open source Radio Browser API.

Application architecture

Here I'm going to document the main architecture of the application. This section is divided into the front-end and backend architecture.

Frontend

App Configuration

Next.js supports environment variables in the front-end at build time. All frontend configuration is set in one file and later used via dependency injection. Take a look at the configuration file or the injection root file.

PWA functionality

PWA functionality is made possible via Workbox library and it is used for:

  • controlling the service worker registration and activation
  • caching of static data and assets
  • notifying the user to install the application
  • notifying the user reload the application when it new version is updated.
  • providing an offline fallback when there is no internet connection

I've also created a small libary that streamlines setting up Workbox with Webpack and Next.js.

State management

State is managed via Mobx. I'm a big fan of reactive architecture and I think it's the future of state management as evidenced by Svelte and SolidJS design decisions.

Mobx stores are architected using the root store pattern (official docs). I have also blogged about using this pattern with Next.js.

I've also created a small abstraction on top of Mobx which implements the collection -> model pattern that handles persisting the data in the application.

Dependency injection - frontend

I've played around with dependency injection to connect all the Mobx stores, and for that, I've used my own small 2KB dependency injection libary that doesn't use decorators for registering and resolving the dependencies, so there is no additional webpack setup, and it can easily be integrated into other libraries and projects.

Take a look at the frontend injection root file.

Note: Some people would argue that the state management is overengineered but the purpose of the application is also to explore different state management patterns and architectures.

User interface

Material design version 4 is used for the user interface. The application has two layouts: desktop and mobile. I've also extracted the app shell layout into separate repository so it can easily be reused as a starting point for building Material UI progressive web apps.

You can test drive template demo here.

Error handling and tracking

Error handling is done via react-error-boundary and error tracking is done via Sentry

Backend

App Configuration

Backend configuration is separate from frontend configuration and it is done via environment variables as per Next.js docs all environment variables are used in only one file, where they are all validated, and default values are added if needed. This also helps with dependency injection when testing.

Take a look at the configuration file.

Dependency injection

Dependency injection is also used in the backend via my library. Using dependency injection significantly eases architecture complexity and testing (no more Jest module mocking).

You can take a look at the backend injection root file.

API routes

I tried something new regarding the Next.js API routes.

If you look into the API directory you will notice that there is only one route (disregarding the NextAuth route) This one and the only route is powered by Koa.js. But to use Koa.js inside the Next.js API routes I've had to wrap Kao.js in a bit of custom code which I've also open sourced.

That one route behaves as a Koa.js server and does all the internal routing, and you can also use all available Koa.js middleware.

Error tracking

Error tracking in the backend is done with Pino logger and [Sentry] error tracking, wrapped in Koa.js middleware.

Take a look at the middleware function file.

Authentication

For authentication NextAuth is used in combination with MongoDB. Currently, users can register with Google and Github accounts.

Testing

Testing is done via Jest and Cypress with Node test containers.

There are three types of tests:

Every time the tests are run, we start MongoDB containers, one DB container per Jest worker file.

I have created an accompanying repository with a distilled example: Test MongoDB and PostgreSQL databases with Jest and Docker containers (blog post is coming soon).

Data generation

In the scripts directory there are a couple of files that are generating special content for the application, generated files are saved in the generated directory and imported into the application like any other modules.

  • Generate countries generates a list of all existing countries and their flags in the form of emoji and also prepares them for translation.
  • Generate genres generates a list of music genres and prepares them for translation.
  • Generate languages generates a list of all existing languages and prepares them for translation.

Patching unmaintained packages

I'm using node-internet-radio to get the "now playing" information from the radio stream. However, there are issues with the package and the maintainer is unresponsive. So I've used the excellent patch-package module to patch (modify) the package myself.

Translation

Multi language support is done via Next.js internationalized routing and LinguiJS internationalization library.

The default language is English and there are translations for Serbian.

While in the development mode, there is also one additional language called "pseudo" which enables you to visually inspect the application and see if you missed translating any of the text.

I've written about internationalizing Next.js Apps for LogRocket blog.

You can also check out the translation demo

If you would like to help me translate the application to more languages please open an issue with the title: <Language>[translation]

Development experience

The repository is set to use VS Code containers or Github Codespaces.

When running via containers everything works (Docker inside Docker yo!) except launching the Cypress application although you can still run all Cypress tests but only in headless mode.

Future

I'm hoping to publish the application to the Google play store.

License

This project is licensed under the MIT License - see the LICENSE file for details