I've been using React for while by mixing it up with .NET stuff and enjoying it a lot. For this post, I decided to combine them along Compose containers, ASP.NET Minimal APIs and MongoDB support!
This post is also available from my blog: https://stvansolano.github.io/blog/
Using React
I use React the tech behind my blog (mostly NodeJS/Jamstack client-side generated which I migrated recently from existing code that has been around ~2015) both for the client and server side rendering.
So this time I decided to set a sample to combine React and .NET in a sample app and ended-up packing it as Pull-Request #248 for the Awesome-Compose repo from Docker in GitHub, following same structure I did for previous .NET contributions into that repo. If you are interested in previous posts about Awesome-Compose, either curious about Docker-izing .NET 5/6 apps or using GitHub Codespaces for development you can browse them from here and here.
The code for this post is also available under my GitHub in this repo:
https://github.com/stvansolano/codebox-react-dotnet-mongodb
So, let's get started! A few things you may need to install:
- Docker Compose
- .NET 6 SDK
- NodeJS
Side-note: I didn't really have to install anything while developing this on my local as I was using GitHub Codespaces with my some tailored devcontainer setup, so I can focus on development!
1) Creating the /frontend: A simple TODO app.
For this sample, we will use react-scripts
scaffold for creating a basic app and use a very simple, still classic sample for having a TODO app in order to focus on JavaScript, NodeJS/Npx stuff. For this sample, it contains also some files I re-used from Awesome-Compose repo:
In order to consume our API later from ASP.NET, let's add a NGINX file for taking care of the API calls later from our ASP.NET Web API. Things will run on their own NGINX-based container so the backend is de-coupled from here.
server {
listen 80;
server_name localhost;
server_tokens off;
proxy_hide_header X-Powered-By;
proxy_hide_header Server;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload';
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-Permitted-Cross-Domain-Policies master-only;
add_header Referrer-Policy same-origin;
add_header Expect-CT 'max-age=60';
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_pass http://backend:8000;
proxy_redirect default;
}
}
Important: The .NET CLI and also Visual Studio provide a great set of pre-defined templates that also include React, Angular and more great stuff, which I encourage to give it a try. Also, pretty much of this tutorial can be also take .NET on the frontend, with the help of amazing Blazor 💖✨
2) Now it's time for .NET in the /backend: Set up Minimal API for MongoDB
Here things become more interesting. By scaffolding a new Web API from .NET 6, we can implement a very-simple, still small program to call MongoDB collection and support some basic operations for our API, and we can add Swagger+OpenAPI support with a few lines of code:
using System;
using System.Linq;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using Models;
using Swashbuckle.AspNetCore.SwaggerGen;
var builder = WebApplication.CreateBuilder(args);
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
string connectionString = builder.Configuration.GetConnectionString("DocumentDbConnection");
string databaseName = builder.Configuration.GetConnectionString("DocumentDbName") ?? "BackendMongoDb";
string collectionName = builder.Configuration.GetConnectionString("DocumentCollectionName") ?? "ToDos";
builder.Services.AddTransient((_provider) => new MongoClient(connectionString));
var app = builder.Build();
var isSwaggerEnabledFromConfig = bool.TrueString.Equals(builder.Configuration["EnableSwagger"] ?? "", StringComparison.OrdinalIgnoreCase);
if (isSwaggerEnabledFromConfig)
{
Console.WriteLine("Swagger enabled via appsettings.json");
}
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment() || isSwaggerEnabledFromConfig)
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.MapGet("/api/todos", async (MongoClient connection) =>
{
try
{
var database = connection.GetDatabase(databaseName);
var collection = database.GetCollection(collectionName);
var results = await collection.Find(_ => true).ToListAsync().ConfigureAwait(false);
return Results.Ok(results);
}
catch (Exception ex)
{
return Results.Problem(detail: ex.ToString());
}
});
app.MapGet("/api/todos/{id}", async (string id, MongoClient connection) =>
{
try
{
var database = connection.GetDatabase(databaseName);
var collection = database.GetCollection(collectionName);
var result = await collection.FindAsync(record => record.Id == id).ConfigureAwait(false) as ToDo;
if (result is null)
{
return Results.NotFound();
}
return Results.Created($"/todoitems/{result.Id}", result);
}
catch (Exception ex)
{
return Results.Problem(detail: ex.ToString());
}
});
app.MapPost("/api/todos", async (ToDo record, MongoClient connection) =>
{
try
{
var database = connection.GetDatabase(databaseName);
var collection = database.GetCollection(collectionName);
await collection.InsertOneAsync(record).ConfigureAwait(false);
return Results.Created($"/api/todos/{record.Id}", record);
}
catch (Exception ex)
{
return Results.Problem(detail: ex.ToString());
}
});
app.Run();
Here is a screenshot of the backend when opening the /swagger endpoint:
3) Oh! Yes, MongoDB. Let's Compose it and wrap up with .NET!
Last but not least, let's have the Frontend in place, connect it to the Backend and store our To-Dos in MongoDB.
We can use Compose services here by just grabbing the container images and set things up.
services:
frontend:
build:
context: frontend
ports:
- 80:80
volumes:
- '.:/app'
- '/app/node_modules'
networks:
- react-frontend
depends_on:
- backend
links:
- backend
backend:
build: backend
restart: always
ports:
- 8000:8000
depends_on:
- mongo
links:
- mongo
environment:
- ASPNETCORE_URLS=http://+:8000
- EnableSwagger=true
networks:
- react-backend
- react-frontend
mongo:
restart: always
image: mongo:4.2.0
volumes:
- ./data:/data/db
networks:
- react-backend
mongo-express:
image: mongo-express
restart: always
ports:
- 8081:8081
environment:
ME_CONFIG_MONGODB_ADMINUSERNAME: root
ME_CONFIG_MONGODB_ADMINPASSWORD: example
depends_on:
- mongo
links:
- mongo
networks:
- react-backend
networks:
react-backend: {}
react-frontend: {}
The compose file also includes a Mongo-Express server in the Compose file so we can quickly explore the NoSQL documents and check everything end-to-end from UI in React either Swagger document generated for us with .NET
Bonus tip: CosmosDB support out of the box!
We can take advantage of using Azure CosmosDB, which supports MongoDB connector so it's pretty easy to set-up and swap from vanilla MongoDB to use CosmosDB. Here I include some links for reference along with more samples to follow-up:
- https://docs.microsoft.com/en-us/azure/cosmos-db/mongodb/connect-using-mongoose/
- https://docs.microsoft.com/en-us/azure/cosmos-db/mongodb/mongodb-introduction/
- https://devblogs.microsoft.com/cosmosdb/build-a-node-js-app-with-azure-cosmos-dbs-api-for-mongodb/
Happy coding!
Top comments (0)