BFF / Frontend workshop
๐ก Project templates to compare stacks around the BFF & SPA pattern (Backend For Frontend & Single Page Application)
Stacks
ASP NET - Blazor
Express - Angular
Nextjs - React
Nuxt - Vue
Actix - Yew (coming soon ...)
BFF (Backend For Frontend):
BFF, short for Backend For Frontend, is a software architectural pattern used in the development of web applications. It involves the creation of dedicated backend services tailored to specific frontend applications or client devices. The primary purpose of a BFF is to optimize the communication between the frontend and backend components by providing tailored endpoints and data structures that precisely match the requirements of the frontend application.
Key Features:
-
Customized API Endpoints: A BFF exposes a set of API endpoints specifically designed to fulfill the needs of the frontend application. These endpoints are structured to provide only the data and functionality required by the frontend, minimizing unnecessary data transfer and processing.
-
Optimized Data Formatting: BFFs format data in a way that is most suitable for consumption by the frontend, reducing the need for additional processing or transformation on the client side. This includes tasks such as data aggregation, filtering, and pagination.
-
Performance Enhancement: By tailoring backend services to the requirements of the frontend, BFFs can improve the performance of web applications. This optimization reduces latency and enhances the overall responsiveness of the user interface.
-
Security and Authorization: BFFs enforce security measures and access control mechanisms specific to the frontend application. This ensures that only authorized users can access the data and functionality exposed by the backend services.
-
Aggregation of Backend Services: In cases where the frontend application needs to interact with multiple backend services, a BFF can serve as an aggregation layer, consolidating data and functionality from various sources into a single interface for the client.
-
Flexibility and Scalability: BFF architecture allows for flexibility in adapting backend services to the evolving needs of the frontend application. It also facilitates the scalability of the system by enabling independent scaling of backend services tailored to different frontend components or versions.
-
Improved Development Workflow: Separating backend services based on frontend requirements simplifies the development workflow by enabling frontend and backend teams to work more independently. This leads to faster iterations, easier debugging, and better overall collaboration between teams.
In summary, BFF (Backend For Frontend) is a software architectural pattern that enhances the performance, security, and development workflow of web applications by tailoring backend services to the specific needs of frontend applications or client devices.
Specification
To add a "BFF/Frontend" stack reference to this repository, here is the following specifications :
Material UI
- Add material UI design system
- color primary by techno
- layout with app bar & collapsable sidemenu to rail
Custom endpoint
- Expose custom server endpoint to "/version"
- return { version: "1.0.0" }
Server side rendering
- Expose frontend web application with SSR
- pre-rendering page with data
- fallback into interactive UI
OIDC & cookie authentication
- Handle OIDC auth over keycloak SSO
- Cookie & antiforgery token
- Login / Logout clean process
Proxying API
- Proxying downsteam REST API
- Proxying jsonplaceholder "todos" API with route "/api/todos"
- https://jsonplaceholder.typicode.com/
- Page listing posts exposed by API
GraphQL gateway
- Gateway GraphQL over downstream subgraph
- Gateway over "countries" graph trevorblades
- https://countries.trevorblades.com/
- Page listing continents exposed by API
Auto generated SDK
- SDK to consume BFF GraphQL schema
- Auto generate it from BFF url
- Use it in frontend application
Light / Dark theme
- Theme switcher implementation
I18N
- I18N switcher : FR & EN
Feature management
- BFF expose feature management configuration
- Enable / disable flag from config and show "User" page link in sidemenu accordingly (A/B testing)
Roadmap
ASP.NET - Blazor
- โ Material UI
- โ Custom endpoint
- โ Server side rendering
- โ OIDC / Cookie authentication
- โ Proxying API
- โ GraphQL gateway
- โ Auto generated SDK
- โ Light / Dark theme
- โ I18N
- โ Feature management
Express - Angular
- โ Material UI
- โ Custom endpoint
- โ Server side rendering
- โ OIDC / Cookie authentication (to ๐งน)
- โ Proxying API
- ๐ ๏ธ GraphQL gateway
- ๐ซ Auto generated SDK
- ๐ซ Light / Dark theme
- ๐ซ I18N
- โ Feature management
Nuxt - Vue
- โ Material UI
- โ Custom endpoint
- โ Server side rendering
- ๐ ๏ธ OIDC / Cookie authentication
- โ Proxying API
- ๐ซ GraphQL gateway
- ๐ซ Auto generated SDK
- ๐ซ Light / Dark theme
- ๐ซ I18N
- ๐ซ Feature management
Nextjs - React
- ๐ ๏ธ Material UI
- โ Custom endpoint
- โ Server side rendering
- ๐ซ OIDC / Cookie authentication
- ๐ ๏ธ Proxying API
- ๐ซ GraphQL gateway
- ๐ซ Auto generated SDK
- ๐ซ Light / Dark theme
- ๐ซ I18N
- ๐ซ Feature management
Yew - Rust (coming)
- ๐ซ Material UI
- ๐ซ Custom endpoint
- ๐ซ Server side rendering
- ๐ซ OIDC / Cookie authentication
- ๐ซ Proxying API
- ๐ซ GraphQL gateway
- ๐ซ Auto generated SDK
- ๐ซ Light / Dark theme
- ๐ซ I18N
- ๐ซ Feature management
Contributing
Requirements
- node 18+ SDK
- dotnet 8 SDK
- docker engine
Building documentation
Open documentation using mdbook
cd ./doc
mdbook serve -p 5555
IAC
cd ./IAC
docker-compose up
Go to keycloak realm admin & regenerate clientSecret for "boilerplate" client
- keycloak realm admin
- Credentials
- Client secret > Regenerate
- Credentials
Then set all your config file with this secret
Blazor
cd ./src/blazor/Microscope.Boilerplate.Clients.BFF
dotnet run
Angular
cd ./src/angular
npm run dev
Nuxt
cd ./src/nuxt
npm run dev
Nextjs
cd ./src/next
npm run dev
Blazor

Blazor is a .NET frontend web framework that supports both server-side rendering and client interactivity in a single programming model
Features
Material UI
- Add material UI design system
- color primary by techno
- layout with app bar & collapsable sidemenu to rail
๐ก Easy intergration using MudBlazor. Excellent DX !
<MudLayout>
<MudAppBar Elevation="1" Color="Color.Primary" Dense="true">
<MudIconButton Icon="@Icons.Material.Outlined.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="((e) => ToggleDrawer())"/>
<MudText>Microscope.Boilerplate.NET</MudText>
@if (!HostingEnvironmentService.IsWebAssembly)
{
<MudProgressCircular Class="ml-2" Size="Size.Small" Color="Color.Inherit" Indeterminate="true"></MudProgressCircular>
}
<MudSpacer/>
<MudIconButton Icon="@Icons.Material.Filled.Brightness4" Color="Color.Inherit" OnClick="ToggleTheme"/>
<MudIconButton Href="https://github.com/bhtz/microscope-boilerplate" Target="_blank" Icon="@Icons.Custom.Brands.GitHub" Color="Color.Inherit"/>
<LoginDisplay />
</MudAppBar>
<MudDrawer @bind-open="DrawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2" Variant="DrawerVariant.Mini" MiniWidth="60px">
<NavMenu/>
</MudDrawer>
<MudMainContent Class="mt-16 pa-4">
@Body
</MudMainContent>
</MudLayout>
private bool DrawerOpen { get; set; } = true;
public void ToggleDrawer()
{
DrawerOpen = !DrawerOpen;
}
Version endpoint
๐ก Expose custom server endpoint to "/version" using a simple asp net minimal API endpoint
app.MapGet("/version", () => new { Version = "1.0.0" });
Server side rendering
- Expose frontend web application with SSR
- pre-rendering page with data
- fallback into interactive UI
๐ก Let blazor handle the magic :
Program.cs
app.MapRazorComponents<Host>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(typeof(_Imports).Assembly);
Host.cs
<body>
<Routes @rendermode="InteractiveAuto" />
<script src="_framework/blazor.web.js"></script>
</body>
OIDC & cookie authentication
- Handle OIDC auth over keycloak SSO
- Cookie & antiforgery token
- Login / Logout clean process
๐ก Simple OIDC asp net authentication
services.AddAuthorization();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.Authority = oidcAuthenticationOptions.Authority;
options.ClientId = oidcAuthenticationOptions.ClientId;
options.ClientSecret = oidcAuthenticationOptions.ClientSecret;
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.ResponseType = OpenIdConnectResponseType.Code;
options.RequireHttpsMetadata = false;
options.SaveTokens = true;
options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = oidcAuthenticationOptions.NameClaimType,
RoleClaimType = oidcAuthenticationOptions.RoleClaimType
};
foreach (var item in oidcAuthenticationOptions.Scopes)
{
options.Scope.Add(item);
}
});
Proxying API
- Proxying downsteam REST API
- Proxying jsonplaceholder "todos" API with route "/api/todos"
- https://jsonplaceholder.typicode.com/
๐ก Using YARP
services
.AddReverseProxy()
.LoadFromConfig(configuration.GetSection("ReverseProxy"));
"ReverseProxy": {
"Routes": {
"post-service" : {
"ClusterId": "post-service",
"Match": {
"Path": "/api/todos/{**catch-all}"
},
"Transforms": [
{
"PathRemovePrefix": "/api"
}
]
}
},
"Clusters": {
"post-service": {
"Destinations": {
"post-service": {
"Address": "https://jsonplaceholder.typicode.com"
}
}
}
}
}
GraphQL gateway
- Gateway GraphQL over downstream subgraph
- Gateway over "countries" graph trevorblades
- https://countries.trevorblades.com/
๐ก Using Hotchocolate GraphQL gateway
GraphQL configuration
var builder = services.AddGraphQLServer();
foreach (var scalar in gatewayOptions.Scalars)
{
builder.AddType(new AnyType(scalar));
}
foreach (var schema in gatewayOptions.Schemas)
{
services
.AddHttpClient(schema.Name, c => c.BaseAddress = new Uri(schema.Url))
.AddHttpMessageHandler<BffAuthenticationHeaderHandler>();
builder.AddRemoteSchema(schema.Name);
var subgraph = services.AddGraphQL(schema.Name);
foreach (var scalar in gatewayOptions.Scalars)
{
subgraph.AddType(new AnyType(scalar));
}
}
builder.AddTypeExtensionsFromFile("./stitching.graphql");
appsettings.development.json
"GraphQLGateway": {
"Scalars" : ["date", "timestamptz", "uuid"],
"Schemas": [
{
"Name": "Countries",
"Url": "https://countries.trevorblades.com/"
}
]
},
Auto generated SDK
- SDK to consume BFF GraphQL schema
- Auto generate it from BFF url
- Use it in frontend application
๐ก Using Hotchocolate Strawberry Shake
cd ./src/blazor/Microscope.Boilerplate.Clients.SDK.GraphQL
dotnet new tool-manifest
dotnet tool install StrawberryShake.Tools
dotnet add package StrawberryShake.Blazor
dotnet graphql init http://localhost:5215/graphql -n BffClient
query GetContinents {
continents {
name
}
}
dotnet build
Add SDK reference to Blazor project
<UseGetContinents Context="result">
<ChildContent>
<MudPaper>
<MudList>
@foreach (var item in result.Continents)
{
<MudListItem Icon="@Icons.Material.Filled.List">@item.Name</MudListItem>
}
</MudList>
</MudPaper>
</ChildContent>
<ErrorContent>
@result.First().Message
</ErrorContent>
<LoadingContent>
<MudSkeleton SkeletonType="SkeletonType.Rectangle" Height="50px" />
<MudSkeleton SkeletonType="SkeletonType.Rectangle" Height="50px" Class="mt-4" />
</LoadingContent>
</UseGetContinents>
Light / Dark theme
- Theme switcher implementation
๐ก Using MudBlazor "MudThemingProvider"
Defining theme
public static MudTheme DarkTheme = new MudTheme()
{
Palette = new Palette()
{
Black = "#27272f",
Background = "#32333d",
BackgroundGrey = "#27272f",
Primary = Colors.Cyan.Darken3,
Surface = "#373740",
DrawerBackground = "#27272f",
DrawerText = "rgba(255,255,255, 0.50)",
AppbarBackground = "#27272f",
AppbarText = "rgba(255,255,255, 0.70)",
TextPrimary = "rgba(255,255,255, 0.70)",
TextSecondary = "rgba(255,255,255, 0.50)",
ActionDefault = "#ffffff",
ActionDisabled = "rgba(255,255,255, 0.26)",
ActionDisabledBackground = "rgba(255,255,255, 0.12)",
DrawerIcon = "rgba(255,255,255, 0.50)"
}
};
Apply theme
<MudThemingProvider Theme="_currentTheme" />
I18N
- I18N switcher : FR & EN
๐ก Using resources files & Localization
server side & client side
services.AddLocalization(options => options.ResourcesPath = "Resources" );
set culture endpoint
app.MapGet("/culture", (HttpContext context, string? culture, string? redirectUri) =>
{
if (culture != null)
{
var requestCulture = new RequestCulture(culture, culture);
var cookieName = CookieRequestCultureProvider.DefaultCookieName;
var cookieValue = CookieRequestCultureProvider.MakeCookieValue(requestCulture);
context.Response.Cookies.Append(cookieName, cookieValue);
}
return Results.Redirect(redirectUri ?? "/");
});
client side set culture based on cookie (for SSR WASM fallback)
var host = builder.Build();
var cookieService = host.Services.GetRequiredService<CookieService>();
var cultureCookie = await cookieService.GetCultureFromCookie();
if (!string.IsNullOrEmpty(cultureCookie))
{
var culture = CookieRequestCultureProvider.ParseCookieValue(cultureCookie)?.Cultures.FirstOrDefault().ToString();
if (culture is not null)
{
var cultureInfo = new CultureInfo(culture);
CultureInfo.DefaultThreadCurrentCulture = cultureInfo;
CultureInfo.DefaultThreadCurrentUICulture = cultureInfo;
}
}
await host.RunAsync();
razor page
@inject IStringLocalizer<Continents> Loc
<MudText Class="mb-8">@Loc["SubTitle"]</MudText>
Feature management
- BFF expose feature management configuration
- Enable / disable flag from config
- A/B testing use case
๐ก Using Microsoft.FeatureManagement.AspNetCore
client page
public class ClientFeatureManagementService(HttpClient client) : IFeatureManagementService
{
public async Task<Dictionary<string, bool>?> GetFeatureManagement()
{
return await client.GetFromJsonAsync<Dictionary<string, bool>>("/api/features");
}
}
server page
public class ServerFeatureManagementService(IFeatureManager featureManager) : IFeatureManagementService
{
public async Task<Dictionary<string, bool>?> GetFeatureManagement()
{
return await featureManager.GetFeatureNamesAsync()
.ToDictionaryAsync(
feature => feature,
feature => featureManager.IsEnabledAsync(feature).GetAwaiter().GetResult());
}
}
endpoint feature management
public static void MapFeatureManagementEndpoints(this WebApplication app)
{
app.MapGet("/api/features", async (IFeatureManagementService featureManagementService) =>
await featureManagementService.GetFeatureManagement());
}
pages or layout
<FeatureFlag FlagName="ShowUserPage">
<MudNavLink Href="user" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.VerifiedUser">@Loc["User"]</MudNavLink>
</FeatureFlag>
Angular

The web development framework for building the future
Features
Material UI
- Add material UI design system
- color primary by techno
- layout with app bar & collapsable sidemenu to rail
๐ก Easy intergration using "@angular/material" package
<mat-toolbar color="primary">
<mat-icon style="cursor: pointer;" (click)="toggle()" aria-hidden="false" aria-label="Example home icon" fontIcon="menu"></mat-icon>
<span style="margin-left: 10px; font-weight: normal; font-size: medium;">Microscope.Boilerplate.Angular</span>
<span class="spacer"></span>
<button *ngIf="!isAuthenticated" mat-icon-button (click)="login()">
<mat-icon>account_circle</mat-icon>
</button>
<button *ngIf="isAuthenticated" mat-button [matMenuTriggerFor]="menu">AD</button>
<mat-menu #menu="matMenu">
<button mat-menu-item (click)="goToAccount()">Account</button>
<button mat-menu-item (click)="logout()">Logout</button>
</mat-menu>
</mat-toolbar>
<mat-sidenav-container class="sidenav-container">
<mat-sidenav class="sidenav" mode="side" [(opened)]="opened" >
<mat-nav-list>
<mat-list-item routerLink="/" routerLinkActive="active"><mat-icon class="v-align" fontIcon="home"></mat-icon><span>Home</span></mat-list-item>
<mat-list-item routerLink="/counter" routerLinkActive="active"><mat-icon class="v-align" fontIcon="add"></mat-icon><span>Counter</span></mat-list-item>
<mat-list-item routerLink="/posts" routerLinkActive="active"><mat-icon class="v-align" fontIcon="list"></mat-icon><span>Posts</span></mat-list-item>
<mat-list-item routerLink="/user" routerLinkActive="active"><mat-icon class="v-align" fontIcon="verified_user"></mat-icon><span>User</span></mat-list-item>
</mat-nav-list>
</mat-sidenav>
<mat-sidenav-content class="sidenav-content">
<router-outlet></router-outlet>
</mat-sidenav-content>
</mat-sidenav-container>
export class AppComponent implements OnInit {
public opened: boolean = true;
// ..
toggle() {
this.opened = !this.opened;
}
// ..
}
Custom endpoint
๐ก Expose custom server endpoint to "/version" using a express API endpoint
server.get('/version', (req, res) => {
res.json({ version: '1.0.0' })
})
Server side rendering
- Expose frontend web application with SSR
- pre-rendering page with data
- fallback into interactive UI
๐ก Let angular universal handle the magic :
server.ts
import { CommonEngine } from '@angular/ssr';
// All regular routes use the Angular engine
server.get('*', (req, res, next) => {
const { protocol, originalUrl, baseUrl, headers } = req;
commonEngine
.render({
bootstrap,
documentFilePath: indexHtml,
url: `${protocol}://${headers.host}${originalUrl}`,
publicPath: browserDistFolder,
providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
})
.then((html) => res.send(html))
.catch((err) => next(err));
});
OIDC & cookie authentication
- Handle OIDC auth over keycloak SSO
- Cookie & antiforgery token
- Login / Logout clean process
๐ก Using angular-oauth2-oidc package for angular authentication
๐จ Authentication is handle here client side & secret token is in the browser ... to improve
oidc.service.ts
import { AuthConfig, OAuthService } from 'angular-oauth2-oidc';
@Injectable({
providedIn: 'root',
})
export class AuthService {
private oAuthService = inject(OAuthService);
private router = inject(Router);
constructor() {
this.initConfiguration();
}
initConfiguration() {
const authConfig: AuthConfig = {
issuer: 'http://localhost:8083/realms/microscope/',
clientId: 'boilerplate',
dummyClientSecret: 'JxaXjmKOd08cMpaKrThAObUzeOmyRiLN',
scope: 'roles',
responseType: 'code',
redirectUri: 'http://localhost:4200/',
strictDiscoveryDocumentValidation: false,
skipIssuerCheck: true
};
this.oAuthService.configure(authConfig);
this.oAuthService.setupAutomaticSilentRefresh();
this.oAuthService.loadDiscoveryDocumentAndTryLogin();
}
login() {
this.oAuthService.initLoginFlow();
}
// ..
}
Proxying API
- Proxying downsteam REST API
- Proxying jsonplaceholder "todos" API with route "/api/todos"
- https://jsonplaceholder.typicode.com/
๐ก Using express-http-proxy package
server.ts
import proxy from 'express-http-proxy';
// ...
server.use('/api', proxy('https://jsonplaceholder.typicode.com'));
Feature management
- BFF expose feature management configuration
- Enable / disable flag from config
- A/B testing use case
๐ก Using a config file & API endpoint
./config/default.json
{
"FeatureManagement": {
"ShowUserPage": true
}
}
./server.ts
import config from 'config';
// ...
server.get('/features', (req, res) => {
let configs = config.get('FeatureManagement') as FeatureFlagResponse;
res.json(configs);
})
./src/feature-flags.service.ts
@Injectable({ providedIn: 'root' })
export class FeatureFlagService {
http = inject(HttpClient);
features = signal<Record<string, boolean>>({});
loadFeatureFlags(): Observable<FeatureFlagResponse> {
return this.http.get<FeatureFlagResponse>('/features').pipe(tap((features) => this.features.set(features)));
}
getFeature(feature: FeatureFlagKeys): boolean {
return this.features()[feature] ?? false;
}
}
type _FeatureFlagKeys = keyof FeatureFlagResponse;
export type FeatureFlagKeys = { [K in _FeatureFlagKeys]: K; }[_FeatureFlagKeys]
export type FeatureFlagResponse = {
ShowUserPage: boolean;
}
./src/app.component.ts
private featureFlagService = inject(FeatureFlagService);
ngOnInit(): void {
// ...
this.featureFlagService.loadFeatureFlags().subscribe(()=> this.isUserPageEnabled = this.featureFlagService.getFeature('ShowUserPage'));
}
./src/app.component.html
<mat-list-item *ngIf="isUserPageEnabled" routerLink="/user" routerLinkActive="active"><mat-icon class="v-align" fontIcon="verified_user"></mat-icon><span>User</span></mat-list-item>
Next

The React Framework for the Web
Features
Nuxt

The Intuitive Vue Framework
Features
Yew
A framework for creating reliable and efficient web applications.