Car Auction App (Narsties) .NET APP / NEXTJS
git log --all --decorate --oneline --graph
git checkout 5b3f518
git checkout main
ADD A GIT ALIAS:
git config --global alias.adog "log --all --decorate --oneline --graph"
git adog
DOTNET
dotnet --version
dotnet --info
dotnet new list
[create new .NET solution] dotnet new sln
[create new .NET Auction Service] dotnet new webapi -o src/AuctionService
[add the Auction Service to the solution we created] dotnet sln add src/AuctionService
code .
[Start the project in watch mode] dotnet watch
[Use CLI to install tools] dotnet tool list -g
dotnet tool install dotnet-ef -g
[Get latest version of a tool] dotnet tool update dotnet-ef -g
CREATE A NEW MIGRATION
The following command will create a migration that will set up the database schema based on the code we have written and the code in the AuctionDBContext class...
dotnet ef migrations add "InitialCreate" -o Data/Migrations
DOCKER
Get Postgres db service running on Docker based on the config in the .yaml file
docker compose up -d
UPDATE THE DB
NOW THAT DATABASE RUNNING we can we can create the tables inside it based on the migration we created above:
dotnet ef database update
We added some SEED Data to the DBInitializer class... lets drop current db and create again with dotnet watch....
dotnet ef database drop
dotnet watch
GIT
(first cd to project root: /Users/deepakbhari/Projects/Narsties)
git init
dotnet new gitignore
--
git add .
git add -A :/
git commit -m "Finished section 2"
Then in github create a new repository.... then ...
git remote add origin git@github.com:deeebeee3/Narsties.git
git push origin main
~/Projects/Narsties ->
dotnet new webapi -o src/SearchService
Make SearchService appear in Solution Explorer:
dotnet sln add src/SearchService
cd SearchService
dootnet build
dotnet watch
https://mongodb-entities.com/wiki/Code-Samples.html#queries
docker compose down
docker compose up -d
rabbitmq: image: rabbitmq:3-management-alpine ports: - 5672:5672 - 15672:15672
un/pw: guest guest
NuGet package manager install MassTransit.RabbitMQ by Chris Patterson
/Users/deepakbhari/Projects/Narsties
dotnet new classlib -o src/src/Contracts
dotnet sln add src/Contracts
cd src/AuctionService dotnet add reference ../../src/Contracts
cd src/SearchService dotnet add reference ../../src/Contracts
NuGet package manager install MassTransit.REntityFrameworkCore by Chris Patterson inside the AuctionService...
dotnet ef migrations add Outbox
https://docs.duendesoftware.com/identityserver/v6/quickstarts/0_overview/
dotnet new --install Duende.IdentityServer.Templates
dotnet new isaspid -o src/IdentityService
when prompted to seed users - choose No (we do this ourself later)
dotnet sln add src/IdentityService
change from https to http....
src/IdentityService/Properties/launchSettings.json
"profiles": { "SelfHost": { "commandName": "Project", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, "applicationUrl": "http://localhost:5000" } } }
Delete the original migrations folder - as it was created for sqlite database and wont work with our postgres database...
dotnet ef migrations add "InitialCreate" -o Data/Migrations
might not need to do the following (only if you get a message in terminal about outdated tools) :
dotnet tool update dotnet-ef -g
copy postgres connection string from auction service and replace one in identity server service with it:
{ "Serilog": { "MinimumLevel": { "Default": "Debug", "Override": { "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information", "Microsoft.AspNetCore.Authentication": "Debug", "System": "Warning" } } },
"ConnectionStrings": { "DefaultConnection": "Server=localhost:5432;User Id=postgres;Password=postgrespw;Database=identity" } }
http://localhost:5000/Account/Login?ReturnUrl=%2Fdiagnostics
bob / Pass123$
Might need to restart app after making changes to Razor pages to make them visible...
hot reload not working properly
jwt.io
dotnet new web -o src/GatewayService
dotnet sln add src/GatewayService
NuGet packages to install:
Yarp.ReverseProxy by Microsoft
Microsoft.AspNetCore.Authentication.JwtBearer by Microsoft
Yarp Docs
https://microsoft.github.io/reverse-proxy/
https://microsoft.github.io/reverse-proxy/articles/config-files.html
"ReverseProxy": { "Routes": { "route1" : { "ClusterId": "cluster1", "Match": { "Path": "{**catch-all}", "Hosts" : [ "www.aaaaa.com", "www.bbbbb.com"], }, } }, "Clusters": { "cluster1": { "Destinations": { "cluster1/destination1": { "Address": "https://example.com/" } } } } }
Install the VSCode Docker extension
FROM mcr.microsoft.com/dotnet/sdk:8.0 as build WORKDIR /app EXPOSE 80
# for every dockerfile in the project to take advantage of docker caching
COPY Narsties.sln Narsties.sln COPY src/AuctionService/AuctionService.csproj src/AuctionService/AuctionService.csproj COPY src/SearchService/SearchService.csproj src/SearchService/SearchService.csproj COPY src/GatewayService/GatewayService.csproj src/GatewayService/GatewayService.csproj COPY src/Contracts/Contracts.csproj src/Contracts/Contracts.csproj COPY src/IdentityService/IdentityService.csproj src/IdentityService/IdentityService.csproj
RUN dotnet restore Narsties.sln
COPY src/AuctionService src/AuctionService COPY src/Contracts src/Contracts
WORKDIR /app/src/AuctionService
RUN dotnet publish -c Release -o /app/src/out
FROM mcr.microsoft.com/dotnet/aspnet:8.0 WORKDIR /app COPY --from=build /app/src/out . ENTRYPOINT [ "dotnet", "AuctionService.dll" ]
docker build -f src/AuctionService/Dockerfile -t testing123 .
(this won't work yet - we need to do some extra configuration) Also we don't want to do it this way - we want to use docker compose to start our containers
docker run testing123
auction-svc: image: deepakbhari/auction-svc:latest build: context: . dockerfile: src/AuctionService/Dockerfile environment: - ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_URLS=http://+:80 - RabbitMqHost=rabbitmq - ConnectionStringsDefaultConnection=Server=postgres:5432;User Id=postgres;Password=postgrespw;Database=auctions - IdentityServiceUrl=http://identity-svc ports: - 7001:80 depends_on: - postgres - rabbitmq
docker compose build auction-svc
docker compose up -d
our auction-svc will be running as a container and it should be connecting to postgres and rabbitmq...
search-svc: image: deepakbhari/search-svc:latest build: context: . dockerfile: src/SearchService/Dockerfile environment: - ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_URLS=http://+:80 - RabbitMqHost=rabbitmq - ConnectionStringsMongoDbConnection=mongodb://root:mongopw@mongodb - AuctionServiceUrl=http://auction-svc ports: - 7002:80 depends_on: - mongodb - rabbitmq
docker compose build search-svc
docker compose up -d
identity-svc: image: deepakbhari/identity-svc:latest build: context: . dockerfile: src/IdentityService/Dockerfile environment: - ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_URLS=http://+:80 - ConnectionStrings__DefaultConnection=Server=postgres:5432;User Id=postgres;Password=postgrespw;Database=identity ports: - 5000:80 depends_on: - postgres
docker compose build identity-svc
docker compose up -d
Note: (if the identity service doesn't start in the mac because port 5000 is already in use uncheck Airplay Receiver in system preferences and try again)
bob Pass123$
see lesson 78 - Debugging a .Netservice in a docker container - we had an error with the following file:
src/IdentityService/Pages/Diagnostics/Index.cshtml.cs
after fixing bug rebuild the image and start the container
docker compose build identity-svc
docker compose up -d
gateway-svc: image: deepakbhari/gateway-svc:latest build: context: . dockerfile: src/GatewayService/Dockerfile environment: - ASPNETCORE_ENVIRONMENT=Docker - ASPNETCORE_URLS=http://+:80 ports: - 6001:80
This line: - ASPNETCORE_ENVIRONMENT=Docker
will read from the appsettings.Docker.json file instead of appsettings.Development.json
for this service....
docker compose build gateway-svc
docker compose up -d
TO CLEAN UP DATA / RESET APPLICATION TO A KNOWN STATE - things out of sync and redo seed do:
docker compose down docker compose up -d
WAS AN ISSUE HERE:
we had to update the following two files:
src/IdentityService/HostingExtensions.cs
docker-compose.yaml
see lesson 80 - testing docker containers....
after fix do:
docker compose build identity-svc
docker compose up -d
cd web-app
npm run dev
code -> setttings -> settings -> search for unknown
CSS › Lint: Unknown At Rules Unknown at-rule.
set to ignore.....
By default anything inside the app folder is considered a server component and rendered on the server side - and return just the html to the client browser
flexboxfroggy.com
Icons:
❯ cd frontend/web-app
npm install react-icons
https://react-icons.github.io/react-icons/
ES7+ React/Redux/React-Native snippets
use rfc shortcut...
For Logging:
In version Next.js 14.0.3 you have to change next.config.js to: /*_ @type {import('next').NextConfig} _/ const nextConfig = { logging: { fetches: { fullUrl: true, }, }, } module.exports = nextConfig
aspect ratio does not always work on browsers - so we will install a tailwindcss plugin which is better:
npm install -D @tailwindcss/aspect-ratio
--
corePlugins: { aspectRatio: false, // disable core aspect ratio - and only use the plugin below... }, plugins: [require("@tailwindcss/aspect-ratio")],
--
replace aspect-video (which did nothing for us) with:
_ IMPORTANT STUFF _
npm install react-countdown
https://www.npmjs.com/package/react-countdown
https://www.npmjs.com/package/react-countdown#custom-renderer-with-completed-condition
--
Nextjs will give the following weird error:
Server Error Error: Super expression must either be null or a function This error happened while generating the page. Any console logs will be displayed in the terminal window.
This is because our React Countdown component is using some client side code (javascript) that our browser needs to do something with, however it is currently being rendered on the server....
We need to convert it to a client side component
add the 'use client' directive at the top of the component to convert it
_ SUPPRESS HYDRATION WARNING _
Error due to small difference between client and server code when using React Countdown:
Warning: Text content did not match. Server: "27" Client: "26" at span at div at Countdown$1
** We dont need to worry about this error...
As the data is coming down to our client and our client component is being hydrated, or our server component is being hydrated with the client side code.
Sometime we will see this error / sometimes not....
So we want to suppress this warning.....
we can do this with:
suppressHydrationWarning={true}
<span suppressHydrationWarning={true}>
{zeroPad(days)}:{zeroPad(hours)}:{zeroPad(minutes)}:{zeroPad(seconds)}
</span>
If we want to use React hooks like useState etc - we need to use client side code... We can create smaller client side components which make use of the hooks and add them inside the server components
Like the CarImage component inside of the AuctionCard component....
TypeScript
https://transform.tools/json-to-typescript
use to this to convert an object to a type...
--
Add the type at the highest place where the data is coming in... in this case our getData function:
async function getData(): Promise<PagedResult> { //this is an extended version of fetch - also gives us caching //this is server side fetching - client won't be aware of this const res = await fetch("http://localhost:6001/search?pageSize=10");
if (!res.ok) throw new Error("Failed to fetch data");
return res.json(); }
Pagination
https://www.flowbite-react.com/docs/components/pagination
https://www.flowbite-react.com/docs/getting-started/quickstart
npm i flowbite-react
Note: flowbite components are all client-side components....
so nextjs will complain if you try to use them in a server-side component.
Workaround - put them in a smaller client-side component and then use that inside the server-side component
STATE MANAGEMENT
Zustand
https://docs.pmnd.rs/zustand/getting-started/introduction
npm install zustand query-string
docker compose build identity-svc
docker compose build search-svc
https://next-auth.js.org/getting-started/example
npm install next-auth
https://next-auth.js.org/configuration/initialization
https://next-auth.js.org/getting-started/typescript#module-augmentation
Protecting routes:
https://next-auth.js.org/tutorials/securing-pages-and-api-routes
https://next-auth.js.org/tutorials/securing-pages-and-api-routes#using-gettoken
npm install react-hook-form react-datepicker
npm install -D @types/react-datepicker
https://react-hook-form.com/get-started
To create reusable inputs see: https://react-hook-form.com/docs/usecontroller
npm install react-hot-toast
~/Projects/Narsties
dotnet new webapi -o src/BiddingService
dotnet sln add src/BiddingService
Nuget packages to add to Bidding Service:
MongoDB.Entities by Đĵ ΝιΓΞΗΛψΚ Microsoft.AspNetCore.Authentication.JwtBearer by Microsoft MassTransit.RabbitMQ by Chris Patterson
cd src/BiddingService
dotnet build // check to see we can build without any errors dotnet watch // start app to check we can connect to rabbitmq and mongodb without any errors
...
Make sure our bidding service has access to the classes inside our Contracts
cd src/BiddingService dotnet add reference ../../src/Contracts
Automapper
Install: AutoMapper.Extensions.Microsoft.DependencyInjection by Jimmy Bogard
GRPC
NuGet install into the AuctionService:
Grpc.AspNetCore by The gRPC Authors
Install vscode-proto3 VSCode extension to help with proto files...
Grpc - Server
add to AuctionsService.csproj file:
dotnet build (so that our grpc code get generated by the proto call buffer)
Grpc - Client
NuGet install into the BiddingService:
Google.Protobuf by Google Inc. Grpc.Tools by The gRPC Authors Grpc.Net.Client by The gRPC Authors
add to BiddingService.csproj file:
dotnet build (so that our grpc code get generated by the proto call buffer)
~/Projects/Narsties
docker compose build
docker compose down
docker compose up -d
~/Projects/Narsties
dotnet new web -o src/NotificationService
dotnet sln add src/NotificationService
cd src/NotificationService
Add a reference to the Contracts
dotnet add reference ../../src/Contracts
NuGet Install:
MassTransit.RabbitMQ by Chris Patterson
Copy masstransit config from another program.cs in another service and copy it into this one...
dotnet build dotnet watch
Update the GatewayService to add the notifications
Create Consumers
They will simply recieve the event and send that event out as a message.
Add and configure CORs in the GatewayService
create Dockefile for NotificationService, copy contents of one from another service, add the NotificationService to it.
Make sure all the Dockerfiles for all services have:
COPY Narsties.sln Narsties.sln COPY src/AuctionService/AuctionService.csproj src/AuctionService/AuctionService.csproj COPY src/SearchService/SearchService.csproj src/SearchService/SearchService.csproj COPY src/GatewayService/GatewayService.csproj src/GatewayService/GatewayService.csproj COPY src/BiddingService/BiddingService.csproj src/BiddingService/BiddingService.csproj COPY src/NotificationService/NotificationService.csproj src/NotificationService/NotificationService.csproj COPY src/Contracts/Contracts.csproj src/Contracts/Contracts.csproj COPY src/IdentityService/IdentityService.csproj src/IdentityService/IdentityService.csproj
update the docker-compose.yaml file and add the config for our new service in here...
notify-svc: image: deepakbhari/notify-svc:latest build: context: . dockerfile: src/NotificationService/Dockerfile environment: - ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_URLS=http://+:80 - RabbitMq__Host=rabbitmq ports: - 7004:80 depends_on: - rabbitmq
~/Projects/Narsties
docker compose build
docker compose down
docker compose up -d
cd frontend/web-app
npm install date-fns
npm install @microsoft/signalr
Once logged out and loggin back in again and session hasnt timed out - if we want to force login add:
{ prompt: "login" }
to:
signIn("id-server", { callbackUrl: "/" }, { prompt: "login" })
In the LoginButton.tsx
Zustand Update
[DEPRECATED] Use createWithEqualityFn instead of create
To fix the above error do the below:
The fix is simply to replace and occurrences of import { create } from "zustand" with import { createWithEqualityFn } from "zustand/traditional" and in the useAuctionStore.ts, useBidStore.ts and useParamsStore.ts, change any call made to the create method to createWithEqualityFn. e.g. export const useAuctionStore = create<State & Actions>((set) => ({ to export const useAuctionStore = createWithEqualityFn<State & Actions>((set) => ({ It's a straight substitution, and console warnings are gone.
docker compose build web-app
docker compose up -d
https://www.hostinger.co.uk/tutorials/how-to-edit-hosts-file
sudo nano /etc/hosts
Add this to bottom of hosts file: 127.0.0.1 id.narsties.com
Like below:
127.0.0.1 localhost 255.255.255.255 broadcasthost ::1 localhost
127.0.0.1 kubernetes.docker.internal
127.0.0.1 id.narsties.com
to match the same url (id.narsties.com) in docker-compose.yaml:
web-app: image: deepakbhari/web-app build: context: . dockerfile: frontend/web-app/Dockerfile volumes: - /var/lib/web/data ports: - 3000:3000 environment: - NEXTAUTH_SECRET=somethingreallyreallysecret - NEXTAUTH_URL=http://localhost:3000 - NEXTAUTH_URL_INTERNAL=http://web-app:3000 - API_URL=http://gateway-svc/ - ID_URL=http://id.narsties.com - NEXT_PUBLIC_NOTIFICATION_URL=http://gateway-svc/notifications
ping id.narsties.com
Make sure we get a response back....

extra_hosts:
- "id.narsties.com:172.20.0.7"
web-app: image: deepakbhari/web-app build: context: . dockerfile: frontend/web-app/Dockerfile volumes: - /var/lib/web/data ports: - 3000:3000 extra_hosts: - "id.narsties.com:172.20.0.7" environment: - NEXTAUTH_SECRET=somethingreallyreallysecret - NEXTAUTH_URL=http://localhost:3000 - NEXTAUTH_URL_INTERNAL=http://web-app:3000 - API_URL=http://gateway-svc/ - ID_URL=http://id.narsties.com - NEXT_PUBLIC_NOTIFICATION_URL=http://gateway-svc/notifications
Then for identity-svc
Change
ports:
- 5000:80
to
ports:
- 80:80
identity-svc: image: deepakbhari/identity-svc:latest build: context: . dockerfile: src/IdentityService/Dockerfile environment: - ASPNETCORE_ENVIRONMENT=Docker - ASPNETCORE_URLS=http://+:80 - ConnectionStrings__DefaultConnection=Server=postgres:5432;User Id=postgres;Password=postgrespw;Database=identity ports: - 80:80 depends_on: - postgres gateway-svc: image: deepakbhari/gateway-svc:latest build: context: . dockerfile: src/GatewayService/Dockerfile environment: - ASPNETCORE_ENVIRONMENT=Docker - ASPNETCORE_URLS=http://+:80 ports: - 6001:80
Now we shpould be able to get to identity server from http://id.narsties.com/ in browser.
❯ docker network ls
NETWORK ID NAME DRIVER SCOPE 58d3891bd610 backend_default bridge local 289ca2735194 bridge bridge local d813033c64f2 host host local f59f0ac3bfdc narsties_default bridge local 72fe355dc0b4 none null local 4bc64bcf9827 server_default bridge local
Create static ip addresses....
First we need to create our own network to do that:
networks: custom: ipam: config: - subnet: 10.5.0.0/16
then add network to bottom of each service...
https://hub.docker.com/r/nginxproxy/nginx-proxy
https://github.com/nginx-proxy/nginx-proxy/tree/main/docs#ssl-support
https://github.com/FiloSottile/mkcert
mkcert is a simple tool for making locally-trusted development certificates.
❯ brew install mkcert
mkcert -h
❯ mkcert -h Usage of mkcert:
$ mkcert -install
Install the local CA in the system trust store.
$ mkcert example.org
Generate "example.org.pem" and "example.org-key.pem".
$ mkcert example.com myapp.dev localhost 127.0.0.1 ::1
Generate "example.com+4.pem" and "example.com+4-key.pem".
$ mkcert "*.example.it"
Generate "_wildcard.example.it.pem" and "_wildcard.example.it-key.pem".
$ mkcert -uninstall
Uninstall the local CA (but do not delete it).
❯ mkcert -install Created a new local CA 💥 Sudo password: The local CA is now installed in the system trust store! ⚡️
~/Pr/Narsties/
❯ mkdir devcerts ❯ cd devcerts
~/Pr/Narsties/devcerts
mkcert -key-file narsties.com.key -cert-file narsties.com.crt app.n arsties.com api.narsties.com id.narsties.com
Created a new certificate valid for the following names 📜
- "app.narsties.com"
- "api.narsties.com"
- "id.narsties.com"
The certificate is at "narsties.com.crt" and the key at "narsties.com.key" ✅
It will expire on 29 April 2026 🗓
FINALLY!!!!