1 - 2025-03-16 | Modern Full-Stack Development with TypeScript

A Comprehensive Seminar on Angular, Nest.js, PostgreSQL, Prisma, Swagger, and Docker


Introduction (10 minutes)

  • Angular - For building dynamic client-side applications
  • Nest.js - For creating structured and scalable server-side applications
  • PostgreSQL - As our reliable and powerful database system
  • Prisma - For type-safe database access
  • Swagger - For API documentation and testing
  • Docker - For containerization and deployment consistency

By the end of this seminar, you’ll understand how these technologies complement each other and how to leverage their combined power to build modern web applications efficiently.


Part 1: Understanding the Stack Architecture (15 minutes)

The Big Picture

Let’s start by understanding how these technologies fit together:

  1. Angular: Handles the presentation layer, user interactions, and client-side logic
  2. Nest.js: Manages server-side business logic, API endpoints, and server operations
  3. PostgreSQL: Stores and manages application data
  4. Prisma: Bridges the gap between our TypeScript code and the database
  5. Swagger: Documents our API for easier consumption and testing
  6. Docker: Packages everything into consistent, deployable containers

Why This Stack?

This stack offers several advantages:

  • TypeScript Everywhere: From frontend to backend to database access
  • Strong Typing: Catch errors at compile time rather than runtime
  • Modular Architecture: Components are loosely coupled but highly cohesive
  • Scalability: Each piece can scale independently
  • Developer Experience: Consistent patterns and tooling across the stack

Real-World Applications

This stack is ideal for:

  • Enterprise applications
  • Complex business systems
  • Applications requiring scalability
  • Teams with specialized frontend and backend developers
  • Projects emphasizing code quality and maintainability

Part 2: Frontend with Angular (30 minutes)

Angular Overview

Angular is a platform and framework for building single-page client applications using HTML and TypeScript.

Key concepts:

  • Component-based architecture
  • Dependency injection
  • Reactive programming with RxJS
  • Strong typing with TypeScript

Essential Angular Features

  1. Components: The building blocks of Angular applications
  2. Services: Singleton objects for shared functionality
  3. Modules: Organizing code into functional cohesive blocks
  4. Routing: Navigation between different views
  5. Forms: Template-driven and reactive approaches
  6. HttpClient: Communication with backend services

Demo: Building an Angular Component

// product.component.ts
import { Component, OnInit } from '@angular/core';
import { ProductService } from '../services/product.service';
import { Product } from '../models/product.model';

@Component({
  selector: 'app-product-list',
  templateUrl: './product-list.component.html',
  styleUrls: ['./product-list.component.scss']
})
export class ProductListComponent implements OnInit {
  products: Product[] = [];
  loading = false;
  error: string | null = null;

  constructor(private productService: ProductService) {}

  ngOnInit(): void {
    this.loadProducts();
  }

  loadProducts(): void {
    this.loading = true;
    this.productService.getProducts()
      .subscribe({
        next: (data) => {
          this.products = data;
          this.loading = false;
        },
        error: (err) => {
          this.error = 'Failed to load products';
          this.loading = false;
          console.error(err);
        }
      });
  }
}

Best Practices for Angular

  • Lazy loading modules for better performance
  • State management with NgRx for complex applications
  • Reactive programming patterns
  • Component testing with Jasmine and Karma
  • Style isolation and component encapsulation

Part 3: Backend with Nest.js (30 minutes)

Nest.js Overview

Nest.js is a progressive Node.js framework for building efficient, reliable, and scalable server-side applications.

Key concepts:

  • Heavily inspired by Angular architecture
  • Modular design
  • Dependency injection
  • Decorators for metadata
  • Middleware support

Essential Nest.js Features

  1. Controllers: Handle incoming requests
  2. Providers/Services: Implement business logic
  3. Modules: Organize application structure
  4. Pipes: Transform and validate input data
  5. Guards: Control access to routes
  6. Interceptors: Transform responses and handle errors

Demo: Building a Nest.js Controller and Service

// products.controller.ts
import { Controller, Get, Post, Body, Param, UseGuards } from '@nestjs/common';
import { ProductsService } from './products.service';
import { CreateProductDto } from './dto/create-product.dto';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';

@ApiTags('products')
@Controller('products')
export class ProductsController {
  constructor(private readonly productsService: ProductsService) {}

  @Get()
  @ApiOperation({ summary: 'Get all products' })
  @ApiResponse({ status: 200, description: 'Return all products.' })
  findAll() {
    return this.productsService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.productsService.findOne(+id);
  }

  @Post()
  @UseGuards(JwtAuthGuard)
  create(@Body() createProductDto: CreateProductDto) {
    return this.productsService.create(createProductDto);
  }
}

// products.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateProductDto } from './dto/create-product.dto';

@Injectable()
export class ProductsService {
  constructor(private prisma: PrismaService) {}

  async findAll() {
    return this.prisma.product.findMany();
  }

  async findOne(id: number) {
    return this.prisma.product.findUnique({
      where: { id },
    });
  }

  async create(data: CreateProductDto) {
    return this.prisma.product.create({
      data,
    });
  }
}

Best Practices for Nest.js

  • Exception filters for consistent error handling
  • Validation using class-validator
  • Environment configuration management
  • Testing with Jest
  • Logging strategies

2 - 2025-03-23 | Modern Full-Stack Development with TypeScript (Cont.)

Introduction

This seminar is the second part of the Modern Full-Stack Development with TypeScript seminar.

Part 4: Database Layer with PostgreSQL and Prisma (25 minutes)

PostgreSQL Overview

PostgreSQL is a powerful, open-source object-relational database system with over 30 years of active development.

Key features:

  • Strong standards compliance
  • Extensibility
  • Robust transaction support
  • Multi-version concurrency control
  • Advanced data types and indexing

Prisma: Modern Database Access

Prisma is an open-source database toolkit that includes:

  1. Prisma Client: Auto-generated and type-safe query builder
  2. Prisma Migrate: Declarative data modeling and migration system
  3. Prisma Studio: GUI to view and edit data

Demo: Defining a Data Model with Prisma

// schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  password  String
  role      Role     @default(USER)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  orders    Order[]
}

model Product {
  id          Int      @id @default(autoincrement())
  name        String
  description String?
  price       Decimal  @db.Decimal(10, 2)
  stock       Int      @default(0)
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
  orderItems  OrderItem[]
}

model Order {
  id        Int         @id @default(autoincrement())
  userId    Int
  user      User        @relation(fields: [userId], references: [id])
  status    OrderStatus @default(PENDING)
  createdAt DateTime    @default(now())
  updatedAt DateTime    @updatedAt
  items     OrderItem[]
}

model OrderItem {
  id        Int     @id @default(autoincrement())
  orderId   Int
  order     Order   @relation(fields: [orderId], references: [id])
  productId Int
  product   Product @relation(fields: [productId], references: [id])
  quantity  Int
  price     Decimal @db.Decimal(10, 2)
}

enum Role {
  USER
  ADMIN
}

enum OrderStatus {
  PENDING
  PROCESSING
  SHIPPED
  DELIVERED
  CANCELLED
}

Connecting Prisma to Nest.js

// prisma.service.ts
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  async onModuleInit() {
    await this.$connect();
  }

  async onModuleDestroy() {
    await this.$disconnect();
  }
}

Best Practices for Database Management

  • Transaction management
  • Database migration strategies
  • Query optimization
  • Connection pooling
  • Data validation and integrity
  • Seeding strategies for development and testing

Part 5: API Documentation with Swagger (15 minutes)

Swagger Overview

Swagger (OpenAPI) provides a standard, language-agnostic interface to RESTful APIs.

Benefits:

  • Interactive documentation
  • Client SDK generation
  • Standardized API description
  • Testing capabilities

Integrating Swagger with Nest.js

// main.ts
import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // Global validation pipe
  app.useGlobalPipes(new ValidationPipe({ transform: true }));
  
  // Swagger setup
  const config = new DocumentBuilder()
    .setTitle('E-commerce API')
    .setDescription('The e-commerce API description')
    .setVersion('1.0')
    .addTag('e-commerce')
    .addBearerAuth()
    .build();
    
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document);
  
  await app.listen(3000);
}
bootstrap();

Documenting API Endpoints

  • Using decorators to document controllers and methods
  • Describing data transfer objects (DTOs)
  • Adding authentication requirements
  • Managing API versions

Part 6: Containerization with Docker (20 minutes)

Docker Overview

Docker provides a way to package applications with all their dependencies into standardized units called containers.

Benefits:

  • Consistent environments
  • Isolation
  • Portability
  • Efficiency
  • Scalability

Dockerizing Our Application

# Dockerfile
FROM node:16 AS builder

WORKDIR /app

# Copy package files and install dependencies
COPY package*.json ./
RUN npm ci

# Copy source files and build application
COPY . .
RUN npm run build

# Production stage
FROM node:16-alpine

WORKDIR /app

# Copy built application from builder stage
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package*.json ./

# Set environment variables
ENV NODE_ENV production
ENV PORT 3000

# Expose application port
EXPOSE 3000

# Start the application
CMD ["node", "dist/main"]

Docker Compose for Multi-Container Setup

# docker-compose.yml
version: '3.8'

services:
  api:
    build:
      context: ./backend
      dockerfile: Dockerfile
    ports:
      - '3000:3000'
    depends_on:
      - postgres
    environment:
      - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/myapp
      - NODE_ENV=production
      - JWT_SECRET=your_jwt_secret
    restart: always

  client:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      - '80:80'
    depends_on:
      - api
    restart: always

  postgres:
    image: postgres:14
    ports:
      - '5432:5432'
    environment:
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_USER=postgres
      - POSTGRES_DB=myapp
    volumes:
      - postgres-data:/var/lib/postgresql/data
    restart: always

volumes:
  postgres-data:

Best Practices for Containerization

  • Multi-stage builds for smaller images
  • Using specific versions for stability
  • Security considerations
  • Resource constraints
  • Container orchestration with Kubernetes

Part 7: Integration and Deployment (15 minutes)

Bringing Everything Together

  1. CI/CD Pipeline Setup

    • GitHub Actions or GitLab CI
    • Build and test automation
    • Docker image creation and pushing
    • Deployment to cloud platforms
  2. Environment Management

    • Managing environment variables
    • Development, staging, and production configurations
    • Secrets management
  3. Monitoring and Logging

    • Health checks and monitoring
    • Centralized logging
    • Performance metrics

Deployment Options

  • AWS, Google Cloud, or Azure
  • Kubernetes clusters
  • Cloud database services
  • Managed Kubernetes services

Conclusion and Q&A (15 minutes)

Summary of Key Points

  • Angular and Nest.js provide a consistent TypeScript experience
  • Prisma simplifies database interactions with type safety
  • PostgreSQL offers a robust and reliable data store
  • Swagger enhances API documentation and testing
  • Docker ensures consistency across environments

Resource List

  • Official documentation links
  • GitHub repositories with sample code
  • Recommended books and courses
  • Community resources and forums

Open Floor for Questions

Thank you for attending! I’m now open to questions about any aspect of the stack we’ve covered today.


Additional Workshop Materials (Optional Add-ons)

Hands-on Lab Instructions

Step-by-step guides for:

  • Setting up the development environment
  • Creating an Angular application
  • Building a Nest.js API
  • Configuring Prisma with PostgreSQL
  • Integrating Swagger documentation
  • Containerizing with Docker

Code Templates and Starter Projects

  • GitHub repository links to starter templates
  • Common patterns and solutions
  • Best practices implementation examples

Angular Series


3 - 2025-03-30 | Modern Full-Stack Development with TypeScript (Cont.)

Introduction

This seminar is the final part of our Modern Full-Stack Development with TypeScript series.

Part 8: Deploy Docker using Ansible (25 minutes)

Ansible Overview

Ansible is an open-source automation tool that simplifies application deployment, configuration management, and task automation.

Key benefits:

  • Agentless architecture (only requires SSH)
  • YAML-based playbooks for easy readability
  • Idempotent operations (safe to run multiple times)
  • Extensive module library
  • Infrastructure as Code approach

Why Ansible for Docker Deployment?

  1. Consistency: Ensure identical deployments across environments
  2. Automation: Reduce manual steps and human error
  3. Scalability: Easily deploy to multiple servers
  4. Orchestration: Coordinate complex deployment sequences
  5. Configuration Management: Handle environment-specific settings

Setting Up Ansible

# inventory.yml
all:
  hosts:
    production:
      ansible_host: 192.168.1.100
      ansible_user: deploy
    staging:
      ansible_host: 192.168.1.101
      ansible_user: deploy
  vars:
    ansible_python_interpreter: /usr/bin/python3

Creating an Ansible Playbook for Docker Deployment

# deploy-docker.yml
---
- name: Deploy Application with Docker
  hosts: all
  become: true
  vars:
    app_name: fullstack-app
    docker_compose_dir: /opt/{{ app_name }}
    env: "{{ lookup('env', 'DEPLOY_ENV') | default('staging', true) }}"
  
  tasks:
    - name: Install required packages
      apt:
        name:
          - docker.io
          - docker-compose
          - python3-pip
        state: present
        update_cache: yes
    
    - name: Ensure Docker service is running
      service:
        name: docker
        state: started
        enabled: yes
    
    - name: Create application directory
      file:
        path: "{{ docker_compose_dir }}"
        state: directory
        mode: '0755'
    
    - name: Copy docker-compose file
      template:
        src: templates/docker-compose.{{ env }}.yml.j2
        dest: "{{ docker_compose_dir }}/docker-compose.yml"
    
    - name: Copy environment variables
      template:
        src: templates/.env.{{ env }}.j2
        dest: "{{ docker_compose_dir }}/.env"
        mode: '0600'
    
    - name: Pull latest Docker images
      command:
        cmd: docker-compose pull
        chdir: "{{ docker_compose_dir }}"
    
    - name: Deploy with docker-compose
      command:
        cmd: docker-compose up -d
        chdir: "{{ docker_compose_dir }}"
    
    - name: Prune unused Docker images
      command: docker image prune -af
      register: prune_result
      changed_when: "'Total reclaimed space:' in prune_result.stdout"

Environment-Specific Configuration Templates

# templates/docker-compose.production.yml.j2
version: '3.8'

services:
  api:
    image: {{ docker_registry }}/{{ app_name }}-api:{{ api_version }}
    restart: always
    ports:
      - "3000:3000"
    environment:
      NODE_ENV: production
      DATABASE_URL: ${DATABASE_URL}
      JWT_SECRET: ${JWT_SECRET}
    depends_on:
      - postgres
    networks:
      - app-network
    deploy:
      replicas: 2
      update_config:
        parallelism: 1
        delay: 10s
  
  client:
    image: {{ docker_registry }}/{{ app_name }}-client:{{ client_version }}
    restart: always
    ports:
      - "80:80"
    depends_on:
      - api
    networks:
      - app-network
  
  postgres:
    image: postgres:14
    volumes:
      - postgres-data:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_USER: ${DB_USER}
      POSTGRES_DB: ${DB_NAME}
    networks:
      - app-network
    restart: always

networks:
  app-network:
    driver: bridge

volumes:
  postgres-data:
    driver: local

Ansible Roles for Reusable Components

Organize your Ansible code into roles for better maintainability:

roles/
├── docker/
│   ├── tasks/
│   │   └── main.yml
│   └── handlers/
│       └── main.yml
├── nginx/
│   ├── tasks/
│   │   └── main.yml
│   ├── templates/
│   │   └── nginx.conf.j2
│   └── handlers/
│       └── main.yml
└── app/
    ├── tasks/
    │   └── main.yml
    ├── templates/
    │   ├── docker-compose.yml.j2
    │   └── .env.j2
    └── defaults/
        └── main.yml

Continuous Deployment with Ansible and CI/CD

# .github/workflows/deploy.yml
name: Deploy Application

on:
  push:
    branches: [main]
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.10'
      
      - name: Install Ansible
        run: |
          python -m pip install --upgrade pip
          pip install ansible          
      
      - name: Set up SSH key
        uses: webfactory/ssh-agent@v0.7.0
        with:
          ssh-private-key: ${{ secrets.DEPLOY_SSH_KEY }}
      
      - name: Run Ansible playbook
        run: |
          ansible-playbook -i inventory.yml deploy-docker.yml          
        env:
          DEPLOY_ENV: production
          ANSIBLE_HOST_KEY_CHECKING: False

Best Practices for Ansible Deployments

  1. Vault for Secrets: Use Ansible Vault to encrypt sensitive data

    ansible-vault create secrets.yml
    ansible-playbook deploy.yml --ask-vault-pass
    
  2. Dynamic Inventories: Use cloud provider plugins for dynamic server discovery

  3. Idempotency: Ensure playbooks can be run multiple times without side effects

  4. Tags: Use tags to run specific parts of your playbook

    ansible-playbook deploy.yml --tags "update,restart"
    
  5. Handlers: Use handlers for actions that should only run when a change occurs

  6. Testing: Test playbooks with Molecule before production deployment

Monitoring and Maintenance

  • Set up regular health checks using Ansible
  • Create playbooks for common maintenance tasks:
    • Database backups
    • Log rotation
    • Certificate renewal
    • Security updates

Rollback Strategies

# rollback.yml
---
- name: Rollback to previous version
  hosts: all
  become: true
  vars:
    app_name: fullstack-app
    docker_compose_dir: /opt/{{ app_name }}
    previous_version: "{{ lookup('env', 'PREVIOUS_VERSION') }}"
  
  tasks:
    - name: Update docker-compose with previous version
      lineinfile:
        path: "{{ docker_compose_dir }}/docker-compose.yml"
        regexp: "image: .*/{{ app_name }}-api:.*"
        line: "    image: {{ docker_registry }}/{{ app_name }}-api:{{ previous_version }}"
    
    - name: Restart with previous version
      command:
        cmd: docker-compose up -d
        chdir: "{{ docker_compose_dir }}"

Conclusion and Next Steps (15 minutes)

Summary of the Full-Stack TypeScript Journey

  • Angular for dynamic frontend experiences
  • Nest.js for structured backend development
  • PostgreSQL and Prisma for type-safe data management
  • Swagger for API documentation
  • Docker for containerization
  • Ansible for automated deployment
  • Serverless architectures
  • Edge computing
  • WebAssembly
  • Micro-frontends
  • GraphQL adoption
  • AI-assisted development

Continuous Learning Resources

  • Community forums and Discord servers
  • Advanced courses and certifications
  • Open-source contribution opportunities
  • Tech conferences and meetups

Q&A Session

Open floor for questions about any aspect of the full-stack TypeScript ecosystem we’ve covered throughout this seminar series.


4 - 2025-04-06 | Modern Full-Stack Development with TypeScript (Final)