Creating a media server interface with React and Emby API

Introduction

In this article, I present an exciting project that I recently realized: a media server interface built with React, using the Emby API to retrieve and display data. This project also implements a hexagonal architecture to ensure a modular organization and a clear separation of responsibilities. 

During this article, we will see all the main steps of creating this project, how to work with the Emby API and how the hexagonal architecture was applied to structure the project. So let’s get started!

Presentation of the project

My goal was to develop an interface that would allow users to easily use their multimedia hosted on their Emby server. Although Emby already offers an integrated user interface, I wanted to create my own version using React and to explore new architectural approaches.

I’m sharing the project repository just below:

Github repository

git clone

EMBY API

What is EMBY

Emby is a multimedia server that allows you to organize, manage and access your digital content such as movies, TV series and music through a user-friendly interface.

The Emby API allows developers to interact with the Emby server and access its features. It is based on HTTP requests and JSON responses, making it easy to use and integrate into various applications, including React projects. An API key is required to access data from the Emby API

How to authenticate with Emby API

Go to Settings, Advanced/Api Keys:

Create a new Api key and update the value of VITE_EMBY_SERVER_API_KEY on your .env

Go to Dashboard:

Get the Remote (WAN) access value and update VITE_EMBY_SERVER_API_URL on your .env 

Once the .env file is completed, you are now ready to make API calls. Here’s how the values are used with Axios.

import axios from 'axios';


const apiKey = import.meta.env.VITE_EMBY_SERVER_API_KEY;


const axiosInstance = axios.create({
   baseURL: import.meta.env.VITE_EMBY_SERVER_API_URL,
   timeout: 5000,
   headers: {
       'Content-Type': 'application/json'
   }
});


axiosInstance.interceptors.request.use(
   config => {
       config.url = `${config.url}?api_key=${apiKey}`;
       return config;
   },
   error => {
       return Promise.reject(error);
   }
);

HEXAGONAL ARCHITECTURE

What is Hexagonal architecture

Hexagonal architecture, also known as port and adapter architecture, is an architectural model that aims to separate the concerns of a system into different layers to ensure modular organization and clear separation of responsibilities. This approach facilitates code maintenance, dependency management and project scalability.

Implementing hexagonal architecture in the project.

Here’s how I’ve structured my project and we’ll take a closer look at how the Category entity has been managed.

Directories structure

Application

This layer contains the business logic of the application and defines the ports, which are the interfaces between the different layers.

This service is responsible for interacting with our models and performing actions on them

//application/services/CategoryService.ts
import { CategoryRepository } from '@domain/repositories/CategoryRepository';

/**
* A service for interacting with categories.
* @param repository - The repository to use for data access.
* @returns A CategoryRepository object with methods for retrieving categories and resumes.
*/
export const categoryService = (repository: CategoryRepository): CategoryRepository => ({
   /**
    * Retrieves all categories.
    * @returns An array of Category objects.
    */
   getCategories: () => {
       return repository.getCategories();
   },

});

Domain

This layer is responsible for communicating with external data sources, such as the Emby API in this case.

 //domain/models/Category.ts
 export type Category = {
   id: string;
   name: string;
   image: string;
   type: string;
 };
// domain/repositories/CategoryRepository.ts
 import { Category } from '@domain/models/Category';

 /**
 * Represents a repository for categories.
 */
 export interface CategoryRepository {
   getCategories: () => Promise<Category[]>;
   getCategoryById: (id: string) => Promise<Category[]>;
 }

Infrastructure

This layer provides the services and utilities needed to run the application, such as HTTP request management, state management, and application configuration.

// domain/infrastructure/http/dto/CategoryDTO.ts

/**
* Represents a DTO for a category.
*/
export interface CategoryDTO {
   Id: string;
   Name: string;
   CollectionType: string;
   ParentBackdropItemId: String;
}
import { Http } from '@domain/repositories/Http';
import { Category } from '@domain/models/Category';
import { CategoryRepository } from '@domain/repositories/CategoryRepository';
import { CategoryDTO } from '@infrastructure/http/dto/CategoryDTO';
import { EMBY_API_KEY, EMBY_API_URL, EMBY_USER_ID } from '@infrastructure/constants/global';

/**
* Defines a categoryRepository function that takes a client of type Http and returns a CategoryRepository object
* @param client - an Http client
* @returns a CategoryRepository object
*/
export const categoryRepository = (client: Http): CategoryRepository => ({
   /**
    * Retrieves all categories
    * @returns an array of Category objects
    */
   getCategories: async () => {
       const categories = await client.get<CategoryDTO>('Library/MediaFolders');
       return categories.Items.map((categoryDto: CategoryDTO): Category => {
           return {
               id: categoryDto.Id,
               name: categoryDto.Name,
               image: `${EMBY_API_URL}/emby/Items/${categoryDto.Id}/Images/Primary?api_key=${EMBY_API_KEY}`,
               type: categoryDto.CollectionType
           };
       });
   },
});

Presentation

This layer includes the React components responsible for display and user interaction.

And now, the component can call the categoryRepository to fetch all categories

import { useCallback, useEffect, useState } from 'react';
import { useNavigate } from 'react-router';

import { categoryService } from '@application/services/CategoryService';
import { Category } from '@domain/models/Category';
import { categoryRepository } from '@infrastructure/repositories/categoryRepository';
import { httpAxios } from '@infrastructure/instances/httpAxios';

const Header = () => {
   const [categories, setCategories] = useState<Category[] | null>();

   const navigate = useNavigate();

   const getCategories = useCallback(async () => {
       try {
           const responseProducts = await  categoryService(categoryRepository(httpAxios)).getCategories();
           setCategories(responseProducts);
       } catch (exception) {
           console.error(exception);
       }
   }, []);

Conclusion

During this project, we saw how to create a media server interface using React and the Emby API, while applying the hexagonal architecture to structure the application. 

However the implementation of this architecture has also presented some challenges, such as the need to understand the basic concepts and the structure of each layer. But once mastered, I think it allows one to create a well organized, modular and easier to maintain application.

I hope this article has inspired you to explore the possibilities offered by React, Emby, and hexagonal architecture, and to consider using them in your own projects.

Would you like to read more articles by Tekos’ Team? Everything’s here.

Reference

Emby:

Tekflix:

Author

Marie Avatar

Leave a comment

Your email address will not be published. Required fields are marked *

Comment
Name
Email
Website

Skip to content