In this exercise we will learn how to create scalable architecture design using Nx. We will create multiple libraries of all types (feature, data-access, ui, util) and scopes (movies, shared).
In the end our project graph should look like this:
As we have to refactor a monolith to a scalable separated architecture, let's stick to the migration strategy discussed before:
- low hanging fruits 🍏: Code that doesn’t have any dependencies (mostly utils, models, types)
- Move code that only depend on libraries (ui only, data-access)
- Move features into feature libraries (pages, routing)
Note
From here on, we are using the REAL project, not the one you have created in the exercises before Please make sure you have set it up properly, by following the instructions:
Tip
If you feel like you don't want to refactor anymore, please go ahead and check out the full solution branch: solutions/libs-arch
Of course it would be amazing if you create the whole structure yourself, but it might take too long for the scope of the workshop. So please
just end the exercise whenever you feel like you don't want to refactor anymore.
git switch -c ${YOUR_BRANCH_NAME} origin/solutions/libs-arch
Tip
You can try to introduce some buildable libraries if you like ;)
Tip
You want to commit often. Please don't push to main, but commit locally. You anyway want to create a new branch later on. You can also just follow the create-a-branch exercise right now and create it upfron.
We want to have the following folder structure in the end
apps/
movies/
libs/
movies/
feature-navbar/
feature-favorites-page/
feature-home-page/
feature-movie-detail/
ui-movie-card/
data-access/
util-movie-search/
shared/
auth/
data-access/
feature-guarded-route/
ui-not-authenticated-page/
util-core/
ui/
models/
feature-error-page/
Create the following libraries:
shared-models
shared-core-utils
Library creation for models & utils
npx nx generate @nx/react:library shared-models --directory=libs/shared/models
npx nx generate @nx/react:library shared-core-utils --directory=libs/shared/core-utils
See the changes made to the tsconfig.base.json
. They should show you the new paths configured for the
libraries.
Now it's time for the refactoring!
Move the apps/movies/models/movie.model.ts
to the newly created libs/shared/models
lib and export it
from the index.ts
barrel file.
We suggest to not use the search for references
option and break the existing imports.
Move the model
// libs/shared/models/src/index.ts
export { Movie } from './movies.model';
Let's repair the imports with a simple trick!
Search & Replace with the following values:
import.*'.*movies.model'
-> import { Movie } from '@react-monorepo/shared-models'
Yeeey! That should have worked.
Now it's time for the refactoring!
Move the contents of apps/movies/core/
to the newly created libs/shared/core-utils/src/
lib and export it
from the index.ts
barrel file.
We suggest to not use the search for references
option and break the existing imports.
Move the utils
// libs/shared/core-utils/src/index.ts
export { IMAGE_PATH } from './image-path';
export { API_URL } from './api-url';
Let's repair the imports with a simple trick!
Search & Replace with the following values:
import.*'.*core/image-path.*'
-> import { IMAGE_PATH } from '@react-monorepo/shared-core-utils'
import.*'.*core/api.*'
-> import { API_URL } from '@react-monorepo/shared-core-utils'
Yeeey! That should have worked.
Run the nx graph
and take a look and your beautiful new libraries being imported.
Create the following library:
shared-ui
We want to put the favorite-btn.tsx
into the newly created shared library, as it has no other dependency and
is just a generic component.
Library creation for shared ui
npx nx generate @nx/react:library shared-ui --directory=libs/shared/ui
Now it's time for the refactoring!
Move the apps/movies/components/favorite-btn.tsx
& apps/movies/components/favorite-btn.spec.tsx
to the newly created libs/shared/ui/src/
lib and export it
from the index.ts
barrel file.
We suggest to not use the search for references
option and break the existing imports.
Move the shared ui
// libs/shared/ui/src/index.ts
// sorry for this!
import FavoriteButton from './favorite-btn';
export { FavoriteButton };
Let's repair the imports with a simple trick!
Search & Replace with the following values:
import.*'.*favorite-btn.*'
-> import { FavoriteButton } from '@react-monorepo/shared-ui'
Yeeey! That should have worked.
Run the nx graph
and take a look and your beautiful new libraries being imported.
Create the following libraries:
movies-data-access
We want to put the movies.context.tsx
into the newly created scoped library, as it tied to the movies app itself.
Library creation for movies-data-access
npx nx generate @nx/react:library movies-data-access --directory=libs/movies/data-access
Now it's time for the refactoring!
Move the apps/movies/contexts/movies.context.tsx
to the newly created libs/movies/data-access/src/
lib and export it
from the index.ts
barrel file.
We suggest to not use the search for references
option and break the existing imports.
Move the movies.context
// libs/movies/data-access/src/index.ts
export { MovieProvider, useMovies, useFavorites } from './movies.context';
Let's repair the imports with a simple trick!
Search & Replace with the following values:
'.*movies.context.*'
-> '@react-monorepo/movies-data-access'
Yeeey! That should have worked.
Run the nx graph
and take a look and your beautiful new libraries being imported.
E.g.
nx run-many -t test
nx run-many -t lint
Note
The affected commands are likely to not work you expect. It is possible to make it work for you, by following the create-a-branch exercise. You will anyway be required to do so! Have fun
-
Create library for shared models
- Movie (
apps/movies/src/app/models/movies.model.ts
)
- Movie (
-
Create library for shared core utils for API_URL & IMAGE_PATH environment variables
-
Create library for shared auth data-access (
apps/movies/src/app/contexts/auth.context.tsx
) -
Create library for movies utils for searchMoviesByTitle (
apps/movies/src/app/utils/search-movies-by-title.util.ts
) -
Create library for shared ui favorite-btn (
apps/movies/src/app/components/favorite-btn.tsx
) -
Create library for movies data-access (
apps/movies/src/app/contexts/movies.context.tsx
) -
Create library for shared auth data-access (
apps/movies/src/app/contexts/auth.context.tsx
) -
Create library for movies ui movie-card (
apps/movies/src/app/components/movie-card.tsx
) -
Create library for movies feature navigation bar (
apps/movies/src/app/layout/navbar
) -
After creating all the libraries, make sure that the application is still working as expected.
-
Run
nx graph
and see how the dependencies are connected between the libraries and the application.
First, create all libraries that we need for our application. And then we can migrate the code from the app to the libraries, and also fix the imports.
npx nx generate @nx/react:library --name=shared-models --directory=libs/shared/models
npx nx generate @nx/react:library --name=shared-core-utils --directory=libs/shared/core-utils
npx nx generate @nx/react:library --name=shared-ui --directory=libs/shared/ui
npx nx generate @nx/react:library --name=movies-ui --directory=libs/movies/ui
npx nx generate @nx/react:library --name=movies-utils --directory=libs/movies/utils
npx nx generate @nx/react:library --name=shared-auth-data-access --directory=libs/shared/auth
npx nx generate @nx/react:library --name=movies-data-access --directory=libs/movies/data-access
npx nx generate @nx/react:library --name=movies-feature-navbar --directory=libs/movies/feature-navbar
- Move the
api-url.ts
andimage-path.ts
files fromapps/movies/src/app/core
tolibs/shared/core-utils/src
. - Export all their content from the
index.ts
file.export * from './api-url'; export * from './image-path';
- Replace all broken imports with the new path
@react-monorepo/shared-core-utils
. Example:- import { API_URL } from '../core/api-url'; + import { API_URL } from '@react-monorepo/shared-core-utils';
- Move the
favorite-btn
folder fromapps/movies/src/app/components
tolibs/shared/ui/src
. - Export the
favorite-btn
folder from theindex.ts
file. - Replace all broken imports with the new path
@react-monorepo/shared-ui
.- Example:
- import FavoriteButton from './favorite-btn'; + import { FavoriteButton } from '@react-monorepo/shared-ui';
-
Move the
movie-card
folder fromapps/movies/src/app/components
tolibs/movies/ui/src
. -
Export the
movie-card
folder from theindex.ts
file. -
Replace all broken imports with the new path
@react-monorepo/movies-ui
.- Example:
- import MovieCard from '../components/movie-card'; + import { MovieCard } from '@react-monorepo/movies-ui';
-
Update the imports to use:
Movie
from@react-monorepo/shared-models
.IMAGE_PATH
from@react-monorepo/shared-core-utils
.FavoriteButton
from@react-monorepo/shared-ui
.
-
Move the
movies.context.tsx
file fromapps/movies/src/app/contexts
tolibs/movies/data-access/src
. -
Export the
movies.context.tsx
file from theindex.ts
file. -
Replace all broken imports with the new path
@react-monorepo/movies-data-access
.Example:
- import { useFavorites, useMovies } from '../contexts/movies.context'; + import { useFavorites, useMovies } from '@react-monorepo/movies-data-access';
-
Update the imports to use:
Movie
from@react-monorepo/shared-models
.API_URL
from@react-monorepo/shared-core-utils
.
-
Move the
auth.context.tsx
file fromapps/movies/src/app/contexts
tolibs/shared/auth/src
. -
Export the
auth.context.tsx
file from theindex.ts
file. -
Replace all broken imports with the new path
@react-monorepo/shared-auth-data-access
.Example:
- import { AuthContext } from '../contexts/auth.context'; + import { AuthContext } from '@react-monorepo/shared-auth-data-access';
-
Update the imports to use:
Movie
from@react-monorepo/shared-models
.
-
Move the
navbar
folder fromapps/movies/src/app/layout
tolibs/movies/feature-navbar/src
. -
Export the
navbar
folder from theindex.ts
file. -
Replace all broken imports with the new path
@react-monorepo/movies-feature-navbar
.Example:
- import ResponsiveAppBar from '../layout/navbar'; + import { ResponsiveAppBar } from '@react-monorepo/movies-feature-navbar';
-
Update the imports to use:
AuthContext
from@react-monorepo/shared-auth-data-access
.useFavorites
from@react-monorepo/movies-data-access
.