Project Overview
This Netflix clone delivers a full-stack, AI-powered chat experience for discovering and discussing movies. Developers get hands-on exposure to GPT-3.5 integration, secure API design, and a modern Angular frontend—all in one project.
Value Proposition
- Enable conversational movie discovery with OpenAI’s GPT-3.5
- Store and manage user-bot conversations for personalized experiences
- Protect endpoints with JWT authentication and rate limiting
- Learn end-to-end development: from ASP.NET Core APIs to Angular UI
Main Features
- AI Chatbot: Ask questions and get contextual movie suggestions
- Personalized Recommendations: Tailor results based on chat history
- Conversation History: Persist and retrieve past dialogs
- Authentication & Security: JWT-based login, role management
- Rate Limiting: Prevent abuse of AI and recommendation endpoints
- Responsive Frontend: Global chat interface, history panel, suggestion cards
Tech Stack
- Backend
- ASP.NET Core Web API (.NET 6+)
- Entity Framework Core (SQL Server)
- OpenAI GPT-3.5 via HttpClient
- JWT Authentication, Middleware for rate limiting
- Frontend
- Angular 13+ with Angular CLI
- TypeScript, RxJS, Angular Material
- Development Tools
- Visual Studio / VS Code
- Postman or HTTPie for API testing
- Docker (optional containerization)
Quick Start Commands
# Backend: run API on http://localhost:5000
cd NetflixClone.Api
dotnet restore
dotnet run
# Frontend: run UI on http://localhost:4200
cd NetflixClone.Client
npm install
ng serve
When to Use This Project
- Prototype AI-driven chat interfaces for media platforms
- Learn secure, scalable API design with ASP.NET Core
- Explore real-world GPT-3.5 integrations and state management
- Bootstrap a movie recommendation or chatbot MVP for internal tools
Getting Started
Follow these steps to run the Netflix clone locally with seeded data.
Prerequisites
- .NET 7 SDK
- SQL Server (LocalDB or full instance)
- Node.js ≥ 16
- Angular CLI (
npm install -g @angular/cli
)
1. Clone the Repository
git clone https://github.com/Moaz-93/Netflix_Clone.git
cd Netflix_Clone
2. Backend Setup
2.1 Configure Connection String
Edit Netflix.API/appsettings.Development.json
(or appsettings.json
) to point at your SQL Server:
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=NetflixCloneDb;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
2.2 Enable Data Seeding
Ensure Program.cs
invokes the seeder before app.Run()
:
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Netflix.API.Data;
var builder = WebApplication.CreateBuilder(args);
// Register DbContext and Identity
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddDefaultIdentity<ApplicationUser>(options => {/*...*/})
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
var app = builder.Build();
// Migrate and seed
using var scope = app.Services.CreateScope();
var services = scope.ServiceProvider;
var db = services.GetRequiredService<ApplicationDbContext>();
var userMgr = services.GetRequiredService<UserManager<ApplicationUser>>();
var roleMgr = services.GetRequiredService<RoleManager<IdentityRole>>();
await db.Database.MigrateAsync();
await ApplicationDbContextSeed.SeedAsync(db, userMgr, roleMgr);
app.MapControllers();
app.Run();
2.3 Build and Run
cd Netflix.API
dotnet build
dotnet run
The API listens on https://localhost:7140
and auto-seeds roles, admin user, plans and categories.
3. Frontend Setup
3.1 Verify API URL
Netflix/src/environments/environment.ts
already points to the local API:
export const environment = {
production: false,
apiUrl: 'https://localhost:7140/api'
};
3.2 Install Dependencies & Serve
cd Netflix
npm install
npm start
The Angular app serves at http://localhost:4200
and proxies API calls to https://localhost:7140/api
.
4. Verify Installation
- Browse Swagger:
https://localhost:7140/swagger
- Log in as seeded admin:
• Email: admin@netflix.com
• Password: Pa$$w0rd! - Visit the UI: create account, browse movies or categories.
You now have a full local Netflix clone with backend, frontend and seed data ready for development.
Core Concepts & Data Models
This section outlines the primary domain entities, their key properties, and relationships. Understanding these models helps you navigate user management, content operations, subscriptions, payments, and recommendations.
1. Users & Profiles
ApplicationUser
Extends ASP-NET Identity to track OTP state, email verification, roles, and relations.
public class ApplicationUser : IdentityUser
{
public string FullName { get; set; }
public bool IsAdmin { get; set; } = false;
// OTP-based email verification
public string? OtpCode { get; set; }
public DateTime? OtpExpiry { get; set; }
public bool IsEmailVerified { get; set; } = false;
// Navigation
public ICollection<Profile> Profiles { get; set; } = new List<Profile>();
public ICollection<UserSubscription> Subscriptions { get; set; } = new List<UserSubscription>();
}
Profile
Represents a user’s viewing profile (e.g., “Kids”, “John”).
public class Profile
{
public int Id { get; set; }
public string Name { get; set; } // e.g. “John’s Profile”
public DateTime CreatedAt { get; set; } // UTC
public string UserId { get; set; }
public ApplicationUser User { get; set; }
// Favorites for this profile
public ICollection<Favorite> Favorites { get; set; } = new List<Favorite>();
}
2. Subscriptions
SubscriptionPlan
Defines pricing and duration of each plan.
public class SubscriptionPlan
{
public int Id { get; set; }
public string Name { get; set; } // e.g. “Premium”
public decimal Price { get; set; } // Monthly price
public int DurationDays { get; set; } // e.g. 30
}
UserSubscription
Links a user to an active or past plan.
public class UserSubscription
{
public int Id { get; set; }
public string UserId { get; set; }
public ApplicationUser User { get; set; }
public int SubscriptionPlanId { get; set; }
public SubscriptionPlan Plan { get; set; }
public DateTime StartDate { get; set; } // UTC
public DateTime ExpiryDate { get; set; } // UTC
public bool IsActive { get; set; } // StartDate ≤ now < ExpiryDate
}
3. Video Catalog
Video
Represents a movie or show.
public class Video
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Url { get; set; } // Streaming URL
public string ThumbnailUrl { get; set; }
public string Category { get; set; } // e.g. “Sci-Fi”
public double Rating { get; set; } // Average user rating
public bool IsDeleted { get; set; } // Soft delete flag
public DateTime CreatedAt { get; set; } // UTC
}
4. Favorites
Favorite
Join entity between Profile and Video.
public class Favorite
{
public int ProfileId { get; set; }
public Profile Profile { get; set; }
public int VideoId { get; set; }
public Video Video { get; set; }
public DateTime AddedAt { get; set; } // UTC
}
5. Payments
PaymentTransaction
Tracks Paymob/payment provider interactions.
public class PaymentTransaction
{
public int Id { get; set; }
public string OrderId { get; set; } // Internal order reference
public string TransactionId { get; set; } // Provider’s transaction token
public int AmountCents { get; set; }
public string Currency { get; set; } // e.g. “EGP”
public string Status { get; set; } // e.g. “PENDING”, “CAPTURED”
public DateTime CreatedAt { get; set; } // UTC
public DateTime? PaidAt { get; set; } // UTC when captured
}
6. AI-Powered Recommendations
MovieRecommendation
Encapsulates a single recommendation result.
public class MovieRecommendation
{
public int Id { get; set; } // Video Id
public string Title { get; set; }
public string Description { get; set; }
public string ImageUrl { get; set; }
public double Rating { get; set; }
public string Category { get; set; }
public string Reason { get; set; } // Why it’s recommended
}
Relationships Overview
- One
ApplicationUser
→ ManyProfile
- One
Profile
↔ ManyFavorite
↔ OneVideo
- One
ApplicationUser
→ ManyUserSubscription
→ OneSubscriptionPlan
PaymentTransaction
logs each purchase tied to anOrderId
Understanding these models and associations enables you to implement authentication flows, manage subscriptions, curate content, and integrate payments and recommendations seamlessly.
CategoryController: CRUD Operations
Purpose
Manage video categories: list, retrieve, create, update, and delete.
1. List Categories (GET /api/categories)
Supports pagination via pageNumber
and pageSize
query parameters.
Query Parameters
• pageNumber (int, default 1)
• pageSize (int, default 10)
Request
GET /api/categories?pageNumber=1&pageSize=10
Success Response
HTTP 200 OK
{
"data": [
{ "id": 1, "name": "Action", "description": "Fast-paced movies" },
{ "id": 2, "name": "Drama", "description": "Story-driven films" }
],
"pagination": {
"pageNumber": 1,
"pageSize": 10,
"totalCount": 2,
"totalPages": 1
}
}
2. Get Category by ID (GET /api/categories/{id})
Path Parameter
• id (int)
Success Response
HTTP 200 OK
{
"id": 1,
"name": "Action",
"description": "Fast-paced movies"
}
Error Response (not found)
HTTP 404 Not Found
{ "message": "Category not found." }
3. Create Category (POST /api/categories)
Request Payload
{
"name": "Sci-Fi",
"description": "Science fiction films"
}
Success Response
HTTP 201 Created
Location: /api/categories/3
{
"id": 3,
"name": "Sci-Fi",
"description": "Science fiction films"
}
Error Response (validation)
HTTP 400 Bad Request
{ "message": "Name is required." }
4. Update Category (PUT /api/categories/{id})
Path Parameter
• id (int)
Request Payload
{
"name": "Science Fiction",
"description": "Sci-Fi movies and series"
}
Success Response
HTTP 204 No Content
Error Response (not found)
HTTP 404 Not Found
{ "message": "Category not found." }
5. Delete Category (DELETE /api/categories/{id})
Path Parameter
• id (int)
Success Response
HTTP 204 No Content
Error Response (not found)
HTTP 404 Not Found
{ "message": "Category not found." }
C# HttpClient Usage Examples
Necessary Imports
using System.Net.Http;
using System.Net.Http.Json;
using Netflix.API.DTOs.CategoryDtos;
// Fetch paged list of categories
async Task<PagedResponse<CategoryDto>> GetCategoriesAsync(HttpClient client, int pageNumber = 1, int pageSize = 10)
{
var url = $"/api/categories?pageNumber={pageNumber}&pageSize={pageSize}";
return await client.GetFromJsonAsync<PagedResponse<CategoryDto>>(url);
}
// Retrieve a single category by ID
async Task<CategoryDto> GetCategoryByIdAsync(HttpClient client, int id)
{
var response = await client.GetAsync($"/api/categories/{id}");
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<CategoryDto>();
}
// Create a new category
async Task<CategoryDto> CreateCategoryAsync(HttpClient client, CreateCategoryDto dto)
{
var response = await client.PostAsJsonAsync("/api/categories", dto);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<CategoryDto>();
}
// Update an existing category
async Task UpdateCategoryAsync(HttpClient client, int id, UpdateCategoryDto dto)
{
var response = await client.PutAsJsonAsync($"/api/categories/{id}", dto);
response.EnsureSuccessStatusCode();
}
// Delete a category
async Task DeleteCategoryAsync(HttpClient client, int id)
{
var response = await client.DeleteAsync($"/api/categories/{id}");
response.EnsureSuccessStatusCode();
}
Practical Tips
• Validate CreateCategoryDto
and UpdateCategoryDto
on the client to surface errors.
• Use totalCount
from pagination metadata to drive UI paging controls.
• Secure CategoryController
with [Authorize(Roles = "Admin")]
.
• Handle HTTP 404 to inform users when a category doesn’t exist.
Routing and Guards
This section explains how the Angular app defines its routes, applies lazy loading, and secures pages using AuthGuard
.
Overview
- Routes split into public, profile, and admin sections.
- Admin area uses nested child routes and lazy-loaded components.
AuthGuard
protects profile and admin routes, enforcing authentication and admin roles.
Route Configuration (app.routes.ts
)
// Netflix/src/app/app.routes.ts
import { Routes } from '@angular/router';
import { AuthGuard } from './core/guards/auth.guard';
export const appRoutes: Routes = [
// Public pages
{ path: 'login', loadComponent: () => import('./features/auth/login.component')
.then(m => m.LoginComponent) },
{ path: 'register', loadComponent: () => import('./features/auth/register.component')
.then(m => m.RegisterComponent) },
// User profile (requires authentication)
{
path: 'profile',
loadComponent: () => import('./features/profile/profile.component')
.then(m => m.ProfileComponent),
canActivate: [AuthGuard]
},
// Admin area (nested routes, requires admin)
{
path: 'admin',
loadComponent: () => import('./features/admin/admin.component')
.then(m => m.AdminComponent),
canActivate: [AuthGuard],
children: [
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' },
{
path: 'dashboard',
loadComponent: () => import('./features/admin/dashboard.component')
.then(m => m.AdminDashboardComponent)
},
{
path: 'movies',
loadComponent: () => import('./features/admin/movies/movies-statistics.component')
.then(m => m.MoviesStatisticsComponent),
children: [
{ path: '', redirectTo: 'all', pathMatch: 'full' },
{ path: 'all', loadComponent: () => import('./features/admin/movies/admin-movies.component').then(m => m.AdminMoviesComponent) },
{ path: 'published', loadComponent: () => import('./features/admin/movies/published-movies.component').then(m => m.PublishedMoviesComponent) },
{ path: 'archived', loadComponent: () => import('./features/admin/movies/deleted-movies.component').then(m => m.DeletedMoviesComponent) },
{ path: 'add', loadComponent: () => import('./features/admin/movies/add-movie.component').then(m => m.AddMovieComponent) },
]
},
{ path: 'moviedetails/:id', loadComponent: () => import('./features/admin/movies/admin-movie-details.component').then(m => m.AdminMovieDetailsComponent) }
]
},
// Fallback
{ path: '**', redirectTo: 'login' }
];
AuthGuard Behavior
- Checks
authService.isAuthenticated()
. - For any route containing
admin
, verifiesauthService.getCurrentUser().isAdmin
. - Redirects unauthenticated users to
/login
. - Redirects non-admins from
/admin/*
to/profile
.
Guard Registration
// Netflix/src/app/core/guards/auth.guard.ts
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
constructor(private auth: AuthService, private router: Router) {}
canActivate(route: ActivatedRouteSnapshot): boolean {
if (!this.auth.isAuthenticated()) {
this.router.navigate(['login']);
return false;
}
const isAdminRoute = route.url.some(seg => seg.path === 'admin');
if (isAdminRoute && !this.auth.getCurrentUser()?.isAdmin) {
this.router.navigate(['profile']);
return false;
}
return true;
}
}
Lazy Loading Best Practices
- Use
loadComponent
for standalone components orloadChildren
for NgModules. - Keep initial bundle small by deferring admin features until needed.
Navigation Examples
Template Links
<!-- Anywhere in app -->
<a routerLink="/profile">My Profile</a>
<a routerLink="/admin/movies/all">All Movies</a>
Programmatic Navigation
import { Router } from '@angular/router';
@Component({ /* ... */ })
export class SomeComponent {
constructor(private router: Router) {}
goToDashboard() {
this.router.navigate(['admin', 'dashboard']);
}
viewMovie(id: string) {
this.router.navigate(['admin', 'moviedetails', id]);
}
}
Practical Tips
- Always place a
<router-outlet>
inAdminComponent
andMoviesStatisticsComponent
templates. - Use
redirectTo
on empty paths to set sensible defaults. - Protect top-level routes with
canActivate: [AuthGuard]
rather than per child. - Verify your component paths in
Netflix/src/app/features/...
match the route imports exactly.
Implementing Global Exception Handling Middleware
Purpose: Centralize unhandled exception capture and return consistent JSON error responses across the API.
1. ErrorHandlingMiddleware Class
Place this in Middleware/ErrorHandlingMiddleware.cs
. It intercepts exceptions, logs them, and formats a ProblemDetails response.
using System.Net;
using System.Text.Json;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace Netflix.API.Middleware
{
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ErrorHandlingMiddleware> _logger;
public ErrorHandlingMiddleware(RequestDelegate next, ILogger<ErrorHandlingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unhandled exception for {Path}", context.Request.Path);
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
var details = new
{
type = "https://tools.ietf.org/html/rfc7231#section-6.6.1",
title = "An unexpected error occurred.",
status = (int)HttpStatusCode.InternalServerError,
detail = exception.Message
};
var payload = JsonSerializer.Serialize(details);
context.Response.ContentType = "application/problem+json";
context.Response.StatusCode = details.status;
return context.Response.WriteAsync(payload);
}
}
}
2. Registration in Program.cs
In Program.cs
, inject the middleware at the top of the pipeline before MVC:
var builder = WebApplication.CreateBuilder(args);
// … other service registrations
var app = builder.Build();
// Global exception handler
app.UseMiddleware<Netflix.API.Middleware.ErrorHandlingMiddleware>();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
3. Customizing Responses
- Extend
details
object to include a uniquetraceId
:
context.Response.Headers.Add("X-Trace-Id", context.TraceIdentifier);
- Map specific exception types to different HTTP statuses:
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
var (status, title) = exception switch
{
UnauthorizedAccessException _ => ((int)HttpStatusCode.Unauthorized, "Unauthorized"),
KeyNotFoundException _ => ((int)HttpStatusCode.NotFound, "Resource Not Found"),
_ => (500, "An unexpected error occurred.")
};
var details = new { type = $"https://httpstatuses.com/{status}", title, status, detail = exception.Message };
…
}
4. Practical Tips
• Log at the most detailed level you need (e.g., include stack traces in Development only).
• Return minimal error details in Production to avoid leaking internal info.
• Write unit tests that simulate exceptions in controller actions to verify middleware behavior.
• Combine with UseHttpsRedirection()
and UseCors()
before your exception handler to catch all pipeline errors.
Development & Contribution Guide
This guide covers setting up your local environment, running tests, enforcing code style, and deploying the Netflix Clone app.
Prerequisites
- Node.js v16+ and npm
- .NET SDK 7.0+ (backend)
- Angular CLI v15+
- Git
Repository Setup
- Clone the repo
git clone https://github.com/Moaz-93/Netflix_Clone.git cd Netflix_Clone
- Install frontend dependencies
cd client npm install
- Install backend dependencies
cd ../server dotnet restore
Local Development
Backend
- Configure
appsettings.Development.json
with your DB/credentials. - Run the API
cd server dotnet run --project NetflixClone.Api
- The API listens on https://localhost:7140 by default.
Frontend
- Use Angular’s proxy to avoid CORS. In
client/proxy.conf.json
:{ "/api": { "target": "https://localhost:7140", "secure": false, "changeOrigin": true } }
- Start dev server
cd client ng serve --proxy-config proxy.conf.json
- Navigate to http://localhost:4200.
Testing
Frontend Unit Tests
cd client
npm run test
- Runs Karma + Jasmine.
- Watch coverage in
client/coverage/index.html
.
Backend Unit Tests
cd server/tests/NetflixClone.Tests
dotnet test --logger:"console;verbosity=detailed"
Code Style & Linting
Frontend
- ESLint + Prettier enforce TypeScript style.
- Run lint
cd client npm run lint
- Auto-fix issues
npm run lint:fix
Backend
- Use EditorConfig and .NET analyzers in
server/NetflixClone.Api.csproj
. - Format via
dotnet format
Commit Messages
Follow Conventional Commits:
feat(login): add OAuth flow
fix(api): handle null user id
chore(deps): update Newtonsoft.Json to v13
Production Deployment
Frontend Build
- Update
client/src/environments/environment.prod.ts
with your live API URL:export const environment = { production: true, apiUrl: 'https://api.my-netflix.com/v1' };
- Build for prod
cd client ng build --configuration=production
- Serve
client/dist/client
via any static host (NGINX, S3, etc.).
Backend Publish
cd server
dotnet publish NetflixClone.Api -c Release -o ../deploy/api
- Deploy the
deploy/api
folder to your server or container.
End-to-End
- Serve frontend static files with a reverse proxy to
/api
. - Ensure HTTPS certificates are configured.
- Monitor logs and health endpoints (
/health
) in production.
Contribution Workflow
- Fork and branch from
main
git checkout -b feat/your-feature
- Implement code; run all tests and linting.
- Push and open a Pull Request targeting
main
. - Linking issues and following the PR template speeds review.
Please adhere to this guide to ensure smooth development, testing, and deployment of the Netflix Clone application.