Please Remember that this is a hobby project and done in the freetime
- Docker
- Run all services:
docker-compose up
Additional Docker Commands:
- Rebuild all Images (also run e.g. if you have a new dependency):
docker-compose build --no-cache
- Run services with rebuild:
docker-compose up -d --build api frontend postgres postgres_admin
- Remove all images:
docker rmi -f $(docker images -a -q)
- Remove all containers:
docker rm -vf $(docker ps -a -q)
- Remove all Volumes:
docker-compose down -v
- Clear all
docker system prune -a --volumes
- If your container says for backend: 'Can not find Module: "XXX"' then
cd todo-api
andnpm run build
TODO: Health check with NestJS Terminus
TODO: Health check with Terminus for Database
- Create a "docker-compose.yml" file at the top level of the project
- Add a "postgres" database service, so that NestJS can connect to it
- Install the NestJS cli globally
npm install -g @nestjs/cli
- Create the NestJS backend api
nest new todo-api --skip-git
- Make sure that all dependencies are installed:
cd todo-api
andnpm install
- Run the project and try to connect to the api via Postman
npm run start:dev
- Generate all needed modules
- Auth Module:
nest generate module auth
- Todo Module:
nest generate module todo
- All Modules should now be generated and also be imported into the main module
- Auth Module:
- Generate the User Resource (inside the User Module)
- Create the plain User Resource (Module, Controller, Service, Entity and DTO (Domain Transfer Object)) with
cd /src/user
and thennest generate resource user
- Move the generated Classes to new folders, for example the service into a "services" folder in the user module
- Create the plain User Resource (Module, Controller, Service, Entity and DTO (Domain Transfer Object)) with
- Set a global prefix to your NestJS api, so that everything is served under the base path "/api"
- Open the main.ts and add
app.setGlobalPrefix('api');
accordingly
- Open the main.ts and add
- Add Typeorm and the connection to the database
- Add access to environment variables via
npm i --save @nestjs/config
- Add the
ConfigModule.forRoot({isGlobal: true})
to the imports of our "app.module" - Add the Typeorm Packages and the Postgres ("pg") packages via
npm install --save @nestjs/typeorm typeorm pg
- Add the Typeorm Module with the configurations ``
- Add access to environment variables via
- Create a "Dockerfile" so that the api can be started via docker-compose as a service
- Install the Angular CLI globally
npm install -g @angular/cli
- Create the Angular Frontend with
ng new angular-frontend
- Make sure to install all deps:
cd angular-frontend
andnpm install
- Set Up our main modules
- Create Public Module (here we handle Register and Login of the users):
ng generate module public
- Create Private Module (handles Stuff after login, jwt protected):
ng generate module private
- Create Public Module (here we handle Register and Login of the users):
- Create dir
/models
and dir/services
- Create a Service to get a value from the todo-api:
ng generate service test
and modify the return from the backend as Json - Create a proxy.conf to proxy all requests when running
ng serve
for/api
against
our in container todo-api underhttp://api:3000
& add it to our angular.json - Add a Dockerfile & then add the service to our docker-compose file
- Fix the start script in the angular package.json to run inside a container to
"start": "ng serve --host=0.0.0.0 --port 4200 --poll 2000"
Explanation: https://stackoverflow.com/questions/46778868/ng-serve-not-working-in-docker-container The "--poll 2000" is needed to get hot reload working Other solution could be to use for ex. nginx
To be able to later build our Realtime Todo Api, we always need to know who the user is and what roles/rights does he has. For this we provide Endpoints for Login and Register. The Login Endpoint will return a JWT, which will then be attached to every request against the backend and it should always be validated.
- Read the Docs about Authentication: https://docs.nestjs.com/security/authentication
- Install the dependencies via:
npm install --save @nestjs/passport passport passport-jwt passport-local bcrypt @nestjs/jwt
- Create the Jwt Strategy under path
/auth/strategies/jwt.strategy.ts
(read more in the docs at 1.) - Create the Auth Service under path
/auth/services/auth.service.ts
- Create a Guard under path
/auth/guards/jwt.guard.ts
- Update imports/providers/... for the AuthModule (make sure to export the authService, so we can use it in other modules)
- Make sure to add the environment variable for the JWT_SECRET to the docker-compose file
- Import the AuthModule into the UserModule
- Create the User Entity & the User Interface
- Install the package class-validator to check our dto values
npm i --save class-validator class-transformer
- Make sure to enable the class validation in main.js by adding:
app.useGlobalPipes(new ValidationPipe());
- Create the dtos for login and register
- Create the login and register Endpoints and Service Functions
- Run the Api with docker and test it with Postman
- Install Angular Material:
ng add @angular/material
- Remove old TestService
- Create Diretories in
angular-frontend/src/app/public
:/validators
,/components
,/services
- Create a
public-routing.module.ts
for all routes in our public module - Create a
public.private-module.interfaces.ts
for our interfaces - Create the components
LoginComponent
andRegisterComponent
via the cli with the commandng generate component login
andng generate component register
- Update the "public-routing.module.ts" and the "app-routing.module.ts"
- Create the Service
UserService
to make the login and register requests against the aping generate service user
- Add the login and register functions to the UserService
- Import the
MatCardModule, MatFormFieldModule, ReactiveFormsModule, MatInputModule, MatButtonModule
in the PublicModule - Import the
MatSnackbarModule
in the AppModule - Create the html, scss and logic for the LoginComponent
- Cretate the html, scss and logic for the RegisterComponent
- Read the docs: https://docs.nestjs.com/websockets/gateways
- Install necessary deps:
npm i --save @nestjs/websockets @nestjs/platform-socket.io socket.io
- Create file
/todo-api/auth.middleware.ts
- Implement the middleware there to authenticate the user who is making a request
- Add the UserService to the Exports from the user.module
- Here you can read more about lifecycles: https://docs.nestjs.com/faq/request-lifecycle#summary
- Add the Middleware Consumer to the
app.module.ts
(on top level of our application) - Generate a Websocket Gateway via
cd /todo-api/src/todo
-nest generate gateway todo
- Import UserModule and AuthModule to TodoModule
- Test the gateway with postman
- Read what the "@auth0/angular-jwt" package does: https://www.npmjs.com/package/@auth0/angular-jwt?activeTab=readme
- Then install it:
cd angular-frontend
npm i @auth0/angular-jwt
- Update Angular (https://update.angular.io/?v=14.0-15.0) to work with recent package version:
ng update @angular/core@15 @angular/cli@15
andng update @angular/material@15
- Remove all legacy imports after the angular update
- Then install it
cd angular-frontend
npm install socket.io-client
- Add "JwtModule" to Imports from "@auth0/angular-jwt" in "app.module.ts"
- Make sure to save the jwt from the response of our login request (user.service.ts - login())
- Add a "getLoggedInUser()" to our user.service to get the user from the jwt
- Add a "dashboardComponent" to our private module,
cd .\angular-frontend\src\app\private\
&mkdir components
&cd components
&ng g c dashboard
- Add a "PrivateRoutingModule" to our routing module & import it in our "private.module"
- Update the "app-routing.module" and lazy load the private Module
- Update our "login.component" to navigate to the dashboard after login
- NestJS, allow cors in our gateway for our angular requests & update the Auth handling (postman will then not work anymore, because no support for auth so far - to work you have to use a raw connection and send the auth token along)
- Add a "todoService" to our private Module
cd .\angular-frontend\src\app\private\
&mkdir services
&cd services
&ng g s todo
- Call our function from the dashboard onInit and see if everything works
- Create a Card Component,
cd .\angular-frontend\src\app\private\components\
ng g c card
- Create an
private-module.interfaces.ts
file in our private module and create aTodoItem
interface - Add DragDropModule to the imports of our
privateModule
- Implement the example from https://material.angular.io/cdk/drag-drop/examples
- Rename CSS Classes
- Create TestData with the TodoItem interface, eg.g. 'testData: TodoItem[] = [....]'
- Update Styles and Html
- Create a todo interface file in the todo module
src/todo/todo.private-module.interfaces.ts
- Create the Todo Interface
- Create a folder entities in our todo module
cd src/todo
mkdir entities
- Create a file todo.entity.ts
cd entities
touch todo.entity.ts
- Create the todo.entity.ts
- Create the todo.service to handle the todos and save them in the database
- Create a interface for the connection against our service in our
todo.private-module.interfaces.ts
and also add aconnected-user.entity.ts
to handle the connections of a user, so that we can send push messages - Add a
connection.service.ts
to implement the logic for handling and saving our connections - Save the connection to our database when a user connects against our gateway (in our gateway.handleConnection)
- Add the gateway.onHandleDisconnect
- Add in our Angular frontend a listener to the 'todos' event with socket.io
- Add a setup Service in Nestjs to add some todos on startup to our database
- Check that when we connect with our Frontend against the gateway, that we print the Todos for the 'todos' event to our console
- Create new Component
ng g c create-todo
in our Angular Project & don't forget ro reference it in the imports of the private module - Add ElementRef for the
create-todo.component
to our dashboard, as well as a button to open the ref - Create FormGroup in 'create-todo.component'
- Create form in template für 'create-todo.component'
- Log out Form onSubmit()
- Rename
interfaces.ts
toprivate-module.interfaces.ts
and create also a const fileprivate-module.consts.ts
and refactor a bit
- NestJS: Listen @SubscribeMessage('addTodoItem') Event and create a todoItem if the Event is received
- Add Listener in todo.gateway.ts
- Add save Function to our todo.service.ts
- Add findAll() function to connection.service.ts
- Distribute the createdTodoItem to all connected Clients/users
- Angular
- Add a saveTodo() method to our todo.service.ts and emit an event matching to our todo.gateway
- Call this saveTodo() when we hit save in our create-todo.component
- Add a listener to the 'addedTodo' Event so we can catch new Todos and subscribe to it in our dashboard
- Add a behaviorSubject to our todo.service, so that we can subscribe in our dashboard to our items
- Then in our dashboard component fix some stuff, for example how to filter the items
- NestJS:
- Add Listener in todo.gateway.ts and emit the updatedTodo after update to all clients
- Add update Function in todo.service.ts
- Angular:
- Add a listener to our todo.service.ts
- Add a function to send a todoUpdate to our api
- call the todo.service.ts in our dashboard.component, so that we receive updates from our api
- call the updateTodo from our service after our drop() function in the dashboard.component