A showcase of how to build API's (REST, gRPC, GraphQL) using C# .NET.
I chose MFlix as the name for this project because I am using one of the sample databases provided by MongoDB called MFlix. The MFlix database is composed of collections of movie related data. I provide more detail in the MongoDB section where I explain how to get a copy of the MFlix database. Therefore, because of the database name, and the fact that MFlix is a short and catchy name, I decided to go with MFlix.
If you're new to REST, gRPC, and MongoDB. I have a few more projects that I maintain on Github that may be of interest.
The primary purpose of MFlix is to demonstrate how to build gRPC, REST (http), and GraphQL API's using C# and the .NET Framework. In order to do this, I have created a contrived use-case that will allow me to demonstrate how to build different API's for different needs. Each API demonstrates how to address the following requirements:
The secondary purpose of MFlix is to demonstrate how to work with different technologies, frameworks, and libraries using the C# .NET Framework. The different topics that will be demonstrated are as follows:
We are going to pretend to be a fictitious company called MFlix. We are going to pretend to take on the role of the core API development team for MFlix. Our strategy is to provide secure and easy access to data to both internal and external applications. We want to abstract away all direct access to database systems and instead provide high performance API's that can be used by product development teams both internally and external to MFlix.
Based on research, it was determined that there are 3 primary groups of developers with very specific preference in terms of preferred API integration. The use-cases are listed as follows:
Therefore, at a high level, the following deductions have been made in terms of the API's that we need to develop:
Furthermore, we can deduce a high-level architectural diagram that highlights
All the infrastructure that is required for the development of this project will be hosted within Docker
and managed with docker-compose
. The full stack has been defined in the docker-compose.yaml
file in the fabric
folder.
In the image above, you will notice a folder called mongo-seed
that contains 2 files, namely Dockerfile
and mflix.gz
. These 2 files are used to seed the MFlix MongoDB database. Let's examine these 2 files in more detail in the next section.
Initially, MongoDB will be the primary database used to store data. If you are interested in how to get started with MongoDB, I have create a getting started guide that covers hosting options (local, docker, cloud), tools, and some basic commands.
We will also be using SEQ as a central logging server for all the API's being built.
The following 2 files are used as part of seeding the MFlix database:
The file mflix.gz
is a compressed mongodb dump of the MFlix database. The MFlix database is a sample database that is provided as part of the MongoDB Atlas. MongoDB Atlas is a Data as a Service (DaaS) offering, and is hosted in the cloud. There is no installation of MongoDB required and a free tier is available.
I decided to make it even easier to work with this project by obtaining the free MFlix sample dataset and including it as part of the Docker stack. However, if you would like to obtain the original MFlix database, you can do so by following the following steps:
To get started, signup for free by registering for a free tier account here. The free tier entitles you to 512MB storage. Please review the [MongoDB Atlas Documentation] for more information.
Once you have registered and setup your MongoDB instance on Atlas, you will be presented with a dashboard resembling the following image:
Select the ellipses next to the Connect
button. Then select Load Sample Dataset
Take note of sample dataset size. Select Load Sample Dataset
.
Open your terminal (bash/powershell) and create a dump of the MFlix database. Take note that in addition to creating the dump, I also compress the file.
# dump the mflix database to a local file
mongodump --uri="mongodb+srv://your_mongodb_atlas_cluster" --db=sample_mflix --username=mongo --gzip --archive=mflix.gz
In the same terminal window, run the following command to restore the dump to a destination of your choice. Take note that in the command below I am renaming the sample_mflix
database to just mflix
database as part of restore.
# restore mflix database from file
mongorestore --host=localhost --port=27017 --username=dbadmin --nsFrom="sample_mflix.*" --nsTo="mflix.*" --gzip --archive=mflix.gz
This Dockerfile will be used as part of the docker-compose
stack and is used to seed the MongoDB database. It runs the commands to be able to copy the mflix.gz
file and restore it to the Docker hosted MongoDB database.
FROM mongo:4.4-bionic
COPY mflix.gz mflix.gz
CMD mongorestore --host=mflix-mongo --port=27017 --username=dbadmin --password=password --nsFrom="sample_mflix.*" --nsTo="mflix.*" --gzip --archive=mflix.gz
The docker-compose.yaml
file defines the stack that will be used for all MFlix API development. The stack is defined as follows:
Define MongoDB Setup
The service is defined as follows:
# MongoDB Setup
mflix-mongo:
image: mongo:4.4-bionic
container_name: mflix-mongo
restart: unless-stopped
ports:
- 27017:27017
networks:
- default
environment:
- MONGO_INITDB_ROOT_USERNAME=dbadmin
- MONGO_INITDB_ROOT_PASSWORD=password
volumes:
- type: volume
source: mflix-mongo-data
target: /data/db
Define MongoDB Seed Setup
depends on MongoDB Setup to complete first
The service is defined as follows:
# Container to seed MongoDB with MFlix data
mflix-mongo-seed:
container_name: mflix-mongo-seed
build: ./mongo-seed
networks:
- default
depends_on:
- mflix-mongo
Define MongoDB Express Setup
a Web Graphical User Interface for managing MongoDB
depends on MongoDB Seed setup to complete first
take note of the setting ME_CONFIG_MONGODB_SERVER: mflix-mongo
. The value mflix-mongo
must be the same value as the name of the MongoDB service defined above
The service is defined as follows:
# MongoExpress Setup
mongo-express:
image: mongo-express
container_name: mflix-mongo-express
restart: unless-stopped
ports:
- 8081:8081
environment:
ME_CONFIG_MONGODB_ADMINUSERNAME: dbadmin
ME_CONFIG_MONGODB_ADMINPASSWORD: password
ME_CONFIG_MONGODB_SERVER: mflix-mongo
networks:
- default
depends_on:
- mflix-mongo-seed
Define SEQ Setup
SEQ is a logging server and will be used a central logging server for all API's
The service is defined as follows:
# Seq Setup
mflix-seq:
image: datalust/seq:2021.1
container_name: mflix-seq
restart: unless-stopped
ports:
- 8082:80
- 5341:5341
networks:
- default
environment:
- ACCEPT_EULA=Y
volumes:
- type: volume
source: mflix-seq-data
target: /data
Follow the following steps to get up and running:
Use any of the following options to get a copy of the code:
# Download Zip File
wget https://github.com/drminnaar/mflix/archive/refs/heads/main.zip
# Clone
git clone https://github.com/drminnaar/mflix.git
The complete MFlix infrastructure is defined in a docker-compose.yaml
stack. One could use docker-compose commands to manage that stack. However, I think the easiest way to manage the stack is to use a task runner to execute commands relating to the management of the stack. I've decided to use npm for my task runner. Although npm is a package manager for the JavaScript programming language, it happens to make a good simple task runner as well. The tasks are defined as follows:
{
"name": "mflix",
"version": "1.0.0",
"scripts": {
"up": "docker-compose -f ./fabric/docker-compose.yaml up --build --detach && docker-compose -f ./fabric/docker-compose.yaml ps",
"down": "docker-compose -f ./fabric/docker-compose.yaml down --volumes && docker-compose -f ./fabric/docker-compose.yaml ps",
"status": "docker-compose -f ./fabric/docker-compose.yaml ps",
"describe": "docker-compose -f ./fabric/docker-compose.yaml config --services"
}
}
To start infrastructure services,
npm run up
The above command will START the docker-compose.yaml
stack and display a summary of the services afterwards. The summary should display a list of running services. The task is defined as follows:
docker-compose -f ./fabric/docker-compose.yaml up --build --detach && docker-compose -f ./fabric/docker-compose.yaml ps
To stop infrastructure services,
npm run down
The above command will STOP the docker-compose.yaml
stack and display a summary of the services afterwards. The summary should be empty if all services were stopped and removed successfully. The task is defined as follows:
docker-compose -f ./fabric/docker-compose.yaml down --volumes && docker-compose -f ./fabric/docker-compose.yaml ps
To describe infrastructure services,
npm run describe
The above command will use the docker-compose.yaml
stack to display a summary of the stack services. The summary should be empty if all services were stopped and removed successfully. The task is defined as follows:
docker-compose -f ./fabric/docker-compose.yaml config --services
To get the current status of infrastructure services,
npm run status
The above command will use the docker-compose.yaml
stack to display a detailed summary of the stack services with their corresponding status. The summary should be empty if all services were stopped and removed successfully. The task is defined as follows:
docker-compose -f ./fabric/docker-compose.yaml config ps
Alternatively, one can install an npm extension and run the your npm tasks as follows:
There are currently 3 services that will mostly be used. The services are listed as follows:
MongoDB
mongo --host localhost --port 27017 --username dbadmin --password password
MongoDB Express
navigate to http://localhost:8081
SEQ Log Server
navigate to http://localhost:8082
Use any of the following options to start the MFlix gRpc API:
Use dotnet cli command:
dotnet watch run --project ./src/MFlix.GrpcApi/MFlix.GrpcApi.csproj
Use NPM task runner:
npm run start:grpc
See section on how to test GRPC services
Use any of the following options to start the MFlix HTTP API:
Use dotnet cli command:
dotnet watch run --project ./src/MFlix.HttpApi/MFlix.HttpApi.csproj
Use NPM task runner:
npm run start:http
Open Swagger Document in Browser:
http://localhost:5050/swagger
Open Postman Collection:
Find the Movies Postman Collection here
Use any of the following options to start the MFlix GraphQL API:
Use dotnet cli command:
dotnet watch run --project ./src/MFlix.GqlApi/MFlix.GqlApi.csproj
Use NPM task runner:
npm run start:gql
Open Banana Cake Pop to start writing queries and mutations:
http://localhost:5000/graphql
Open Voyager to view API as interactive graph:
http://localhost:5000/voyager
Use NPM task runner to start all API's:
npm run start:apis
# HTTP API: Open Swagger Document in Browser
http://localhost:5050/swagger
# GraphQL API: Open Banana Cake Pop to start writing queries and mutations:
http://localhost:5000/graphql
# GraphQL API: Open Voyager to view API as interactive graph:
http://localhost:5000/voyager
Before testing the gRPC services that are defined by the MFlix gRPC server, you will need the following:
See the Getting Started Section to get more information.
I recommend the following tools for testing gRPC services:
grpcurl
is a command-line tool that lets you interact with gRPC servers. It's basically curl
for gRPC servers. Go here for more information.
You will need to have golang (go) installed before installing gRPCurl
. See the install instructions at the official golang documentation.
For Linux users, you may need to add the following to your ~/.bashrc
file:
# go config
export PATH=$PATH:/usr/local/go/bin
export PATH=$PATH:$GOPATH/bin
In ddition to BASH on Linux, all the following commands have also been tested on the cross-platform (Windows, Linux, and macOS) Powershell.
# install grpcurl using go
go get github.com/fullstorydev/grpcurl/...
go install github.com/fullstorydev/grpcurl/cmd/grpcurl
# after install
grpcurl -version
# OUTPUT:
grpcurl.exe v1.7.0
# INPUT:
grpcurl -h
# INPUT:
grpcurl -plaintext localhost:4000 list
# OUTPUT:
grpc.reflection.v1alpha.ServerReflection
mflix.services.MovieService
# INPUT:
grpcurl -plaintext localhost:4000 describe
# OUTPUT:
grpc.reflection.v1alpha.ServerReflection is a service:
service ServerReflection {
rpc ServerReflectionInfo ( stream .grpc.reflection.v1alpha.ServerReflectionRequest ) returns ( stream .grpc.reflection.v1alpha.ServerReflectionResponse );
}
mflix.services.MovieService is a service:
service MovieService {
rpc DeleteMovie ( .mflix.services.DeleteMovieRequest ) returns ( .mflix.services.DeleteMovieResponse );
rpc GetMovieById ( .mflix.services.GetMovieByIdRequest ) returns ( .mflix.services.GetMovieByIdResponse );
rpc GetMovieList ( .mflix.services.GetMovieListRequest ) returns ( .mflix.services.GetMovieListResponse );
rpc SaveImdbRating ( .mflix.services.SaveImdbRatingRequest ) returns ( .mflix.services.SaveImdbRatingResponse );
rpc SaveMetacriticRating ( .mflix.services.SaveMetacriticRatingRequest ) returns ( .mflix.services.SaveMetacriticRatingResponse );
rpc SaveMovie ( .mflix.services.SaveMovieRequest ) returns ( .mflix.services.SaveMovieResponse );
rpc SaveTomatoesRating ( .mflix.services.SaveTomatoesRatingRequest ) returns ( .mflix.services.SaveTomatoesRatingResponse );
}
#INPUT:
grpcurl -plaintext localhost:4000 describe mflix.services.MovieService
# OUTPUT:
mflix.services.MovieService is a service:
service MovieService {
rpc DeleteMovie ( .mflix.services.DeleteMovieRequest ) returns ( .mflix.services.DeleteMovieResponse );
rpc GetMovieById ( .mflix.services.GetMovieByIdRequest ) returns ( .mflix.services.GetMovieByIdResponse );
rpc GetMovieList ( .mflix.services.GetMovieListRequest ) returns ( .mflix.services.GetMovieListResponse );
rpc SaveImdbRating ( .mflix.services.SaveImdbRatingRequest ) returns ( .mflix.services.SaveImdbRatingResponse );
rpc SaveMetacriticRating ( .mflix.services.SaveMetacriticRatingRequest ) returns ( .mflix.services.SaveMetacriticRatingResponse );
rpc SaveMovie ( .mflix.services.SaveMovieRequest ) returns ( .mflix.services.SaveMovieResponse );
rpc SaveTomatoesRating ( .mflix.services.SaveTomatoesRatingRequest ) returns ( .mflix.services.SaveTomatoesRatingResponse );
}
# INPUT: list methods for MovieService
grpcurl -plaintext localhost:4000 list mflix.services.MovieService
# OUTPUT:
mflix.services.MovieService.DeleteMovie
mflix.services.MovieService.GetMovieById
mflix.services.MovieService.GetMovieList
mflix.services.MovieService.SaveImdbRating
mflix.services.MovieService.SaveMetacriticRating
mflix.services.MovieService.SaveMovie
mflix.services.MovieService.SaveTomatoesRating
# INPUT:
grpcurl -plaintext localhost:4000 describe mflix.services.GetMovieListRequest
# OUTPUT
mflix.services.GetMovieListRequest is a message:
message GetMovieListRequest {
.mflix.services.MovieOptions options = 1;
}
# INPUT:
grpcurl -plaintext localhost:4000 describe mflix.services.MovieOptions
# OUTPUT
mflix.services.MovieOptions is a message:
message MovieOptions {
int32 pageNumber = 1;
int32 pageSize = 2;
repeated string sortBy = 3;
string title = 4;
string rated = 5;
string runtime = 6;
string year = 7;
string type = 8;
repeated string cast = 9;
repeated string genres = 10;
repeated string directors = 11;
}
# INPUT
$request = @'
{
\"movieId\": \"573a1397f29313caabce68f6\"
}
'@
grpcurl -d $request -plaintext localhost:4000 mflix.services.MovieService/GetMovieById
# OUTPUT:
{
"movie": {
"id": "573a1397f29313caabce68f6",
"title": "Star Wars: Episode IV - A New Hope",
"runtime": 121,
"rated": "PG",
"year": 1977,
"poster": "https://m.media-amazon.com/images/M/MV5BNzVlY2MwMjktM2E4OS00Y2Y3LWE3ZjctYzhkZGM3YzA1ZWM2XkEyXkFqcGdeQXVyNzkwMjQ5NzM@._V1_SY1000_SX677_AL_.jpg",
"imdb": {
"rating": 8.7,
"votes": 773938,
"id": 76759
},
"tomatoes": {
"consensus": "A legendarily expansive and ambitious start to the sci-fi saga, George Lucas opened our eyes to the possiblites of blockbuster filmmaking and things have never been the same.",
"critic": {
"rating": 8.5,
"numReviews": 82,
"meter": 94
},
"dvd": "2004-09-21T00:00:00Z",
"fresh": 77,
"lastUpdated": "2015-09-12T17:04:03Z",
"production": "20th Century Fox",
"rotten": 5,
"viewer": {
"rating": 4.1,
"numReviews": 848179,
"meter": 96
},
"website": "http://www.starwars.com/episode-iv/"
},
"cast": [
"Mark Hamill",
"Harrison Ford",
"Carrie Fisher",
"Peter Cushing"
],
"genres": [
"Action",
"Adventure",
"Fantasy"
],
"directors": [
"George Lucas"
]
}
}
# INPUT
$request = @'
{
\"options\": {
\"pageNumber\": 1,
\"pageSize\": 20,
\"sortBy\": [\"-year\"],
\"title\": \"batman\",
\"rated\": \"\",
\"runtime\": \"\",
\"year\": \"gte:2000\",
\"type\": \"\",
\"cast\": [],
\"genres\": [],
\"directors\": []
}
}
'@
grpcurl -d $request -plaintext localhost:4000 mflix.services.MovieService/GetMovieList
# OUTPUT
{
"movies": [ ... ],
"pageInfo": {
"currentPageNumber": 1,
"nextPageNumber": 0,
"previousPageNumber": 0,
"lastPageNumber": 1,
"itemCount": "11",
"pageSize": 20,
"pageCount": 1,
"hasPrevious": false,
"hasNext": false
}
}
# INPUT:
$request = @'
{
\"movie\": {
\"id\": \"\",
\"title\": \"API Wars\",
\"plot\": \"One APIs great struggle against all odds to be the number one API .... in the world!\",
\"runtime\": 120,
\"rated\": \"PG\",
\"year\": 2021,
\"poster\": \"https://picsum.photos/200/300\",
\"released\": \"2021-03-26T00:00:00Z\",
\"genres\": [\"Action\"],
\"cast\": [\"gRPC\",\"REST\",\"GraphQL\"],
\"directors\": [\"Douglas Minnaar\"]
}
}
'@
grpcurl -d $request -plaintext localhost:4000 mflix.services.MovieService/SaveMovie
# OUTPUT:
{
"movieId": "605d5276f164eea829cdc9a0"
}
# INPUT:
$request = @'
{
\"movieId\": \"573a1397f29313caabce68f6\",
\"imdb\": {
\"rating\": 5.4,
\"votes\": 104354,
\"id\": 10384738
}
}
'@
grpcurl -d $request -plaintext localhost:4000 mflix.services.MovieService/SaveImdbRating
# OUTPUT:
{
"imdb": {
"rating": 5.4,
"votes": 104354,
"id": 10384738
}
}
# INPUT:
$request = @'
{
\"movieId\": \"573a1397f29313caabce68f6\",
\"tomatoes\": {
\"boxOffice\": \"\",
\"consensus\": \"A legendary movie about legends\",
\"critic\": {
\"rating\": 1.4,
\"numReviews\": 10,
\"meter\": 10
},
\"dvd\": \"2021-03-26T00:00:00Z\",
\"fresh\": 65,
\"lastUpdated\": \"2021-03-26T00:00:00Z\",
\"production\": \"20th Century Fox\",
\"rotten\": 10,
\"viewer\": {
\"rating\": 16.4,
\"numReviews\": 130,
\"meter\": 1055
},
\"website\": \"http://www.example.com\"
}
}
'@
grpcurl -d $request -plaintext localhost:4000 mflix.services.MovieService/SaveTomatoesRating
# OUTPUT:
{
"tomatoes": {
"consensus": "A legendary movie about legends",
"critic": {
"rating": 1.4,
"numReviews": 10,
"meter": 10
},
"dvd": "2021-03-26T00:00:00Z",
"fresh": 65,
"lastUpdated": "2021-03-26T00:00:00Z",
"production": "20th Century Fox",
"rotten": 10,
"viewer": {
"rating": 16.4,
"numReviews": 130,
"meter": 1055
},
"website": "http://www.example.com"
}
}
# INPUT:
$request = @'
{
\"movieId\": \"573a1397f29313caabce68f6\",
\"metacriticRating\": 89
}
'@
grpcurl -d $request -plaintext localhost:4000 mflix.services.MovieService/SaveMetacriticRating
# OUTPUT
{
"metacriticRating": 89
}
# INPUT:
$request = @'
{
\"movieId\": \"573a1397f29313caabce68f6\"
}
'@
grpcurl -d $request -plaintext localhost:4000 mflix.services.MovieService/DeleteMovie
# OUTPUT
{
"movieId": "573a1397f29313caabce68f6"
}
gRPCUI is a command-line tool that lets you interact with gRPC servers via a browser.
You will need to have golang (go) installed before installing gRPCUI
. See the install instructions at the official golang documentation.
For Linux users, you may need to add the following to your ~/.bashrc
file:
# go config
export PATH=$PATH:/usr/local/go/bin
export PATH=$PATH:$GOPATH/bin
In ddition to BASH on Linux, all the following commands have also been tested on the cross-platform (Windows, Linux, and macOS) Powershell.
# install grpcui using go
go get github.com/fullstorydev/grpcui/...
go install github.com/fullstorydev/grpcui/cmd/grpcui
# after install
grpcui -version
grpcui -plaintext localhost:4000
# OUTPUT:
gRPC Web UI available at http://127.0.0.1:63382/
BloomRPC aims to provide the simplest and most efficient developer experience for exploring and querying your GRPC services.
Find the latest releases here
Mac Install
brew install --cask bloomrpc
Windows Install
# install using chocolatey
choco install bloomrpc
Insomnia is an Open Source API client, and collaborative API design platform for REST, SOAP, GraphQL, and GRPC.
Find a list of downloads here.
For Windows, you can also install with chocolatey
choco install insomnia-rest-api-client