From 415cab89a2d1b9efc4651971cf8d01305ab05fc7 Mon Sep 17 00:00:00 2001 From: Divya Pahuja Date: Wed, 15 Oct 2025 23:57:00 +0530 Subject: [PATCH 1/6] Adding angular docs --- docs/angular/components.md | 80 ++++++++++++++ docs/angular/forms.md | 217 +++++++++++++++++++++++++++++++++++++ docs/angular/routing.md | 167 ++++++++++++++++++++++++++++ docs/angular/services.md | 119 ++++++++++++++++++++ docs/angular/setup.md | 56 ++++++++++ 5 files changed, 639 insertions(+) create mode 100644 docs/angular/components.md create mode 100644 docs/angular/forms.md create mode 100644 docs/angular/routing.md create mode 100644 docs/angular/services.md create mode 100644 docs/angular/setup.md diff --git a/docs/angular/components.md b/docs/angular/components.md new file mode 100644 index 0000000..52a0037 --- /dev/null +++ b/docs/angular/components.md @@ -0,0 +1,80 @@ +# Angular Components + +## What is a Component? +A component controls a patch of screen called a view. It consists of: +- A TypeScript class (component class) +- An HTML template +- Optional CSS styles + +## Creating a Component +```bash +ng generate component my-component +# or shorthand +ng g c my-component +``` + +## Component Structure +```typescript +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-my-component', + templateUrl: './my-component.component.html', + styleUrls: ['./my-component.component.css'] +}) +export class MyComponent { + // Component logic goes here + title = 'My Component'; +} +``` + +## Component Lifecycle Hooks +- `ngOnInit()`: Called after the first ngOnChanges +- `ngOnChanges()`: Called when input properties change +- `ngDoCheck()`: Custom change detection +- `ngAfterViewInit()`: Called after the view is initialized +- `ngOnDestroy()`: Cleanup just before Angular destroys the component + +## Component Communication + +### Parent to Child: @Input() +```typescript +// parent.component.html + + +// child.component.ts +@Input() message: string; +``` + +### Child to Parent: @Output() +```typescript +// child.component.ts +@Output() notify = new EventEmitter(); + +onClick() { + this.notify.emit('Button clicked!'); +} + +// parent.component.html + +``` + +## View Encapsulation +Angular supports three view encapsulation strategies: +- `ViewEncapsulation.Emulated` (default) +- `ViewEncapsulation.None` +- `ViewEncapsulation.ShadowDom` + +## Content Projection +```html + + +

Card Title

+

Card content goes here

+
+ + +
+ +
+``` diff --git a/docs/angular/forms.md b/docs/angular/forms.md new file mode 100644 index 0000000..f0e012f --- /dev/null +++ b/docs/angular/forms.md @@ -0,0 +1,217 @@ +# Angular Forms + +## Template-Driven Forms + +### Basic Setup +```typescript +// app.module.ts +import { FormsModule } from '@angular/forms'; + +@NgModule({ + imports: [ + FormsModule + ] +}) +``` + +### Basic Form +```html +
+
+ + +
+ +
+ + +
+ + +
+``` + +### Two-way Binding +```html + +``` + +## Reactive Forms + +### Setup +```typescript +// app.module.ts +import { ReactiveFormsModule } from '@angular/forms'; + +@NgModule({ + imports: [ + ReactiveFormsModule + ] +}) +``` + +### Basic Form +```typescript +// In component +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; + +export class LoginComponent { + loginForm: FormGroup; + + constructor(private fb: FormBuilder) { + this.loginForm = this.fb.group({ + email: ['', [Validators.required, Validators.email]], + password: ['', [Validators.required, Validators.minLength(6)]] + }); + } + + onSubmit() { + if (this.loginForm.valid) { + console.log(this.loginForm.value); + } + } +} +``` + +```html +
+
+ + +
+ Email is required +
+
+ +
+ + +
+ + +
+``` + +### Form Arrays +```typescript +// In component +export class OrderFormComponent { + orderForm: FormGroup; + + constructor(private fb: FormBuilder) { + this.orderForm = this.fb.group({ + items: this.fb.array([this.createItem()]) + }); + } + + createItem(): FormGroup { + return this.fb.group({ + name: ['', Validators.required], + quantity: [1, [Validators.required, Validators.min(1)]] + }); + } + + get items() { + return this.orderForm.get('items') as FormArray; + } + + addItem() { + this.items.push(this.createItem()); + } + + removeItem(index: number) { + this.items.removeAt(index); + } +} +``` + +### Custom Validators +```typescript +export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn { + return (control: AbstractControl): {[key: string]: any} | null => { + const forbidden = nameRe.test(control.value); + return forbidden ? {forbiddenName: {value: control.value}} : null; + }; +} + +// Usage +this.heroForm = this.fb.group({ + name: ['', [ + Validators.required, + forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator + ]] +}); +``` + +### Dynamic Forms +```typescript +// In component +export class DynamicFormComponent { + form: FormGroup; + fields = [ + { type: 'text', name: 'firstName', label: 'First Name', required: true }, + { type: 'email', name: 'email', label: 'Email', required: true }, + { type: 'password', name: 'password', label: 'Password', required: true } + ]; + + constructor(private fb: FormBuilder) { + const group = {}; + this.fields.forEach(field => { + group[field.name] = ['', field.required ? Validators.required : []]; + }); + this.form = this.fb.group(group); + } +} +``` + +### Form Submission with HTTP +```typescript +// In component +export class UserFormComponent { + userForm: FormGroup; + + constructor(private fb: FormBuilder, private http: HttpClient) { + this.userForm = this.fb.group({ + name: ['', Validators.required], + email: ['', [Validators.required, Validators.email]] + }); + } + + onSubmit() { + if (this.userForm.valid) { + this.http.post('/api/users', this.userForm.value).subscribe( + response => console.log('Success!', response), + error => console.error('Error!', error) + ); + } + } +} +``` + +### Cross-Field Validation +```typescript +// In component +this.form = this.fb.group({ + password: ['', [Validators.required]], + confirmPassword: ['', [Validators.required]] +}, { validator: this.passwordMatchValidator }); + +passwordMatchValidator(g: FormGroup) { + return g.get('password').value === g.get('confirmPassword').value + ? null : { 'mismatch': true }; +} +``` + +### Form Status Changes +```typescript +// In component +ngOnInit() { + this.form.statusChanges.subscribe(status => { + console.log('Form status:', status); + }); + + this.form.get('email').valueChanges.subscribe(value => { + console.log('Email changed to:', value); + }); +} +``` diff --git a/docs/angular/routing.md b/docs/angular/routing.md new file mode 100644 index 0000000..7466240 --- /dev/null +++ b/docs/angular/routing.md @@ -0,0 +1,167 @@ +# Angular Routing + +## Basic Setup + +```typescript +// app-routing.module.ts +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { HomeComponent } from './home/home.component'; +import { AboutComponent } from './about/about.component'; + +const routes: Routes = [ + { path: '', component: HomeComponent }, + { path: 'about', component: AboutComponent }, + { path: '**', redirectTo: '' } // Wildcard route for 404 +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule] +}) +export class AppRoutingModule { } +``` + +## Route Parameters + +```typescript +// Route definition +{ path: 'product/:id', component: ProductDetailComponent } + +// Accessing in component +import { ActivatedRoute } from '@angular/router'; + +constructor(private route: ActivatedRoute) { + this.route.params.subscribe(params => { + this.productId = params['id']; + }); +} +``` + +## Child Routes + +```typescript +const routes: Routes = [ + { + path: 'products', + component: ProductsComponent, + children: [ + { path: '', component: ProductListComponent }, + { path: ':id', component: ProductDetailComponent }, + { path: ':id/edit', component: ProductEditComponent } + ] + } +]; +``` + +## Route Guards + +### CanActivate +```typescript +@Injectable({ + providedIn: 'root' +}) +export class AuthGuard implements CanActivate { + constructor(private authService: AuthService, private router: Router) {} + + canActivate( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot): boolean { + if (this.authService.isLoggedIn()) { + return true; + } + this.router.navigate(['/login']); + return false; + } +} + +// Usage +{ path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] } +``` + +### Resolve +```typescript +@Injectable({ providedIn: 'root' }) +export class ProductResolver implements Resolve { + constructor(private productService: ProductService) {} + + resolve(route: ActivatedRouteSnapshot): Observable { + return this.productService.getProduct(route.paramMap.get('id')); + } +} + +// Usage +{ + path: 'product/:id', + component: ProductDetailComponent, + resolve: { + product: ProductResolver + } +} +``` + +## Lazy Loading + +```typescript +const routes: Routes = [ + { + path: 'admin', + loadChildren: () => import('./admin/admin.module') + .then(m => m.AdminModule) + } +]; +``` + +## Navigation + +```typescript +// In component +constructor(private router: Router) {} + +gotoProduct(id: number) { + this.router.navigate(['/product', id]); + // or with query params + this.router.navigate(['/products'], { + queryParams: { page: 1, search: 'angular' } + }); +} +``` + +## Route Events + +```typescript +constructor(private router: Router) { + this.router.events.pipe( + filter(event => event instanceof NavigationEnd) + ).subscribe((event: NavigationEnd) => { + console.log('Navigation ended:', event.url); + }); +} +``` + +## Route Animations + +```typescript +// In component +@Component({ + selector: 'app-root', + template: ` +
+ +
+ `, + animations: [ + trigger('routeAnimations', [ + transition('* <=> *', [ + style({ opacity: 0 }), + animate('300ms', style({ opacity: 1 })) + ]) + ]) + ] +}) +export class AppComponent { + prepareRoute(outlet: RouterOutlet) { + return outlet?.activatedRouteData?.['animation']; + } +} +``` diff --git a/docs/angular/services.md b/docs/angular/services.md new file mode 100644 index 0000000..ed74973 --- /dev/null +++ b/docs/angular/services.md @@ -0,0 +1,119 @@ +# Angular Services + +## What is a Service? +Services are singleton objects that provide specific functionality throughout the application. They are typically used for: +- Data fetching and sharing +- Business logic +- Logging +- Authentication + +## Creating a Service +```bash +ng generate service data +# or shorthand +ng g s data +``` + +## Basic Service Example +```typescript +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' // Makes it a singleton service +}) +export class DataService { + private data: any[] = []; + + constructor() { } + + getData(): any[] { + return this.data; + } + + addData(item: any): void { + this.data.push(item); + } +} +``` + +## Dependency Injection +```typescript +// In a component +constructor(private dataService: DataService) {} + +// In a module (if not using providedIn: 'root') +@NgModule({ + providers: [DataService] +}) +``` + +## HTTP Client +```typescript +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class ApiService { + private apiUrl = 'https://api.example.com'; + + constructor(private http: HttpClient) {} + + getItems(): Observable { + return this.http.get(`${this.apiUrl}/items`); + } + + addItem(item: any): Observable { + return this.http.post(`${this.apiUrl}/items`, item); + } +} +``` + +## HTTP Interceptors +```typescript +import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +@Injectable() +export class AuthInterceptor implements HttpInterceptor { + intercept(req: HttpRequest, next: HttpHandler): Observable> { + const token = localStorage.getItem('token'); + + if (token) { + const cloned = req.clone({ + headers: req.headers.set('Authorization', `Bearer ${token}`) + }); + return next.handle(cloned); + } + + return next.handle(req); + } +} +``` + +## Error Handling +```typescript +import { catchError } from 'rxjs/operators'; +import { throwError } from 'rxjs'; + +getItems(): Observable { + return this.http.get(`${this.apiUrl}/items`).pipe( + catchError(error => { + console.error('Error fetching items:', error); + return throwError('Something went wrong'); + }) + ); +} +``` + +## Service Lifecycle Hooks +- `ngOnInit()`: Not available in services +- `ngOnDestroy()`: Can be used to clean up subscriptions + +## Best Practices +- Keep services focused on a single responsibility +- Use `providedIn: 'root'` for singleton services +- Handle errors appropriately +- Unsubscribe from observables to prevent memory leaks +- Use interfaces for request/response types diff --git a/docs/angular/setup.md b/docs/angular/setup.md new file mode 100644 index 0000000..431c8b7 --- /dev/null +++ b/docs/angular/setup.md @@ -0,0 +1,56 @@ +# Angular Setup Guide + +## Prerequisites +- Node.js (v14 or later) +- npm (v6 or later) +- Angular CLI (latest stable version) + +## Installation + +1. Install Angular CLI globally: +```bash +npm install -g @angular/cli +``` + +2. Verify installation: +```bash +ng version +``` + +## Creating a New Project +```bash +ng new my-angular-app +cd my-angular-app +``` + +## Project Structure +``` +my-angular-app/ +├── src/ +│ ├── app/ # Your application code +│ ├── assets/ # Static assets +│ ├── environments/ # Environment configurations +│ └── index.html # Main HTML file +├── angular.json # Angular CLI configuration +└── package.json # Project dependencies +``` + +## Development Server +```bash +# Start development server +ng serve + +# Open in browser +ng serve --open +``` + +## Building for Production +```bash +ng build --prod +``` + +## Common Commands +- `ng generate component component-name` - Generate a new component +- `ng generate service service-name` - Generate a new service +- `ng test` - Run unit tests +- `ng e2e` - Run end-to-end tests From ede0f77b6bcaa0b70f1d579296bec6e0140f39c5 Mon Sep 17 00:00:00 2001 From: Divya Pahuja Date: Thu, 16 Oct 2025 00:15:33 +0530 Subject: [PATCH 2/6] adding mongodb docs --- docs/mongodb/aggregation.md | 375 ++++++++++++++++++++++++ docs/mongodb/crud.md | 262 +++++++++++++++++ docs/mongodb/mongoose.md | 536 ++++++++++++++++++++++++++++++++++ docs/mongodb/schema-design.md | 318 ++++++++++++++++++++ docs/mongodb/setup.md | 211 +++++++++++++ 5 files changed, 1702 insertions(+) create mode 100644 docs/mongodb/aggregation.md create mode 100644 docs/mongodb/crud.md create mode 100644 docs/mongodb/mongoose.md create mode 100644 docs/mongodb/schema-design.md create mode 100644 docs/mongodb/setup.md diff --git a/docs/mongodb/aggregation.md b/docs/mongodb/aggregation.md new file mode 100644 index 0000000..a380221 --- /dev/null +++ b/docs/mongodb/aggregation.md @@ -0,0 +1,375 @@ +# MongoDB Aggregation Framework + +## Aggregation Pipeline Stages + +### $match +Filters documents to pass only matching documents to the next stage. + +```javascript +db.orders.aggregate([ + { $match: { status: "completed", total: { $gt: 100 } } } +]); +``` + +### $group +Groups documents by specified identifier expressions. + +```javascript +db.orders.aggregate([ + { + $group: { + _id: "$customerId", + totalSpent: { $sum: "$total" }, + averageOrder: { $avg: "$total" }, + orderCount: { $sum: 1 } + } + } +]); +``` + +### $sort +Sorts all input documents and returns them in sorted order. + +```javascript +db.products.aggregate([ + { $sort: { price: -1, name: 1 } } +]); +``` + +### $project +Reshapes each document by including, excluding, or adding new fields. + +```javascript +db.users.aggregate([ + { + $project: { + name: 1, + email: 1, + yearJoined: { $year: "$joinDate" }, + _id: 0 + } + } +]); +``` + +### $lookup +Performs a left outer join to another collection. + +```javascript +db.orders.aggregate([ + { + $lookup: { + from: "customers", + localField: "customerId", + foreignField: "_id", + as: "customer" + } + }, + { $unwind: "$customer" } +]); +``` + +### $unwind +Deconstructs an array field to output one document per array element. + +```javascript +db.orders.aggregate([ + { $unwind: "$items" }, + { $group: { _id: "$items.productId", total: { $sum: "$items.quantity" } } } +]); +``` + +### $facet +Processes multiple aggregation pipelines within a single stage. + +```javascript +db.products.aggregate([ + { + $facet: { + categories: [ + { $unwind: "$categories" }, + { $group: { _id: "$categories", count: { $sum: 1 } } }, + { $sort: { count: -1 } } + ], + priceStats: [ + { $match: { price: { $exists: true } } }, + { + $group: { + _id: null, + average: { $avg: "$price" }, + min: { $min: "$price" }, + max: { $max: "$price" } + } + } + ] + } + } +]); +``` + +## Aggregation Operators + +### Arithmetic Operators +- `$add`: Adds numbers together +- `$subtract`: Subtracts two numbers +- `$multiply`: Multiplies numbers together +- `$divide`: Divides one number by another +- `$mod`: Returns the remainder of a division + +```javascript +db.orders.aggregate([ + { + $project: { + subtotal: 1, + tax: 1, + total: { + $add: ["$subtotal", "$tax"] + }, + discount: { + $multiply: ["$subtotal", 0.1] // 10% discount + }, + finalTotal: { + $subtract: [ + { $add: ["$subtotal", "$tax"] }, + { $multiply: ["$subtotal", 0.1] } + ] + } + } + } +]); +``` + +### String Operators +- `$concat`: Concatenates strings +- `$substr`: Returns a substring +- `$toLower` / `$toUpper`: Converts to lowercase/uppercase +- `$trim`: Removes whitespace + +```javascript +db.users.aggregate([ + { + $project: { + fullName: { + $concat: ["$firstName", " ", "$lastName"] + }, + emailLower: { $toLower: "$email" }, + username: { $substr: ["$email", 0, { $indexOfBytes: ["$email", "@"] }] } + } + } +]); +``` + +### Date Operators +- `$year` / `$month` / `$dayOfMonth`: Extracts date parts +- `$dateToString`: Formats a date as a string +- `$dateFromString`: Converts a date string to a date object + +```javascript +db.orders.aggregate([ + { + $project: { + orderDate: 1, + year: { $year: "$orderDate" }, + month: { $month: "$orderDate" }, + day: { $dayOfMonth: "$orderDate" }, + formattedDate: { + $dateToString: { + format: "%Y-%m-%d", + date: "$orderDate" + } + } + } + } +]); +``` + +### Conditional Operators +- `$cond`: Ternary operator +- `$ifNull`: Returns a value if null, otherwise returns the field value +- `$switch`: Case/when/default logic + +```javascript +db.products.aggregate([ + { + $project: { + name: 1, + price: 1, + priceCategory: { + $switch: { + branches: [ + { case: { $lt: ["$price", 50] }, then: "Budget" }, + { case: { $lt: ["$price", 200] }, then: "Standard" }, + { case: { $lt: ["$price", 1000] }, then: "Premium" } + ], + default: "Luxury" + } + }, + discountPrice: { + $cond: { + if: { $gt: ["$quantity", 10] }, + then: { $multiply: ["$price", 0.9] }, // 10% discount + else: "$price" + } + }, + description: { $ifNull: ["$description", "No description available"] } + } + } +]); +``` + +## Performance Optimization + +### Index Usage +```javascript +// Create an index for the aggregation +// db.orders.createIndex({ status: 1, orderDate: -1 }); + +db.orders.aggregate([ + { $match: { status: "completed", orderDate: { $gte: new Date("2023-01-01") } } }, + { $sort: { orderDate: -1 } }, + { $limit: 100 } +]); +``` + +### $lookup Optimization +```javascript +// Instead of this (inefficient with large collections): +db.orders.aggregate([ + { $lookup: { from: "products", localField: "productId", foreignField: "_id", as: "product" } }, + { $unwind: "$product" }, + { $match: { "product.category": "Electronics" } } +]); + +// Do this (more efficient): +db.orders.aggregate([ + { + $lookup: { + from: "products", + let: { productId: "$productId" }, + pipeline: [ + { $match: { $expr: { $eq: ["$_id", "$$productId"] }, category: "Electronics" } } + ], + as: "product" + } + }, + { $unwind: "$product" } +]); +``` + +### $redact +Controls document traversal during processing. + +```javascript +db.employees.aggregate([ + { + $redact: { + $cond: { + if: { $eq: ["$department", "HR"] }, + then: "$$DESCEND", + else: "$$PRUNE" + } + } + } +]); +``` + +## Aggregation with $graphLookup + +### Recursive Lookup +```javascript +db.employees.aggregate([ + { + $match: { name: "John Doe" } + }, + { + $graphLookup: { + from: "employees", + startWith: "$reportsTo", + connectFromField: "reportsTo", + connectToField: "_id", + as: "managementChain", + maxDepth: 10, + depthField: "level" + } + } +]); +``` + +## Aggregation with $merge + +### Output to a Collection +```javascript +db.orders.aggregate([ + { $match: { status: "completed" } }, + { + $group: { + _id: { $dateToString: { format: "%Y-%m", date: "$orderDate" } }, + totalSales: { $sum: "$total" }, + orderCount: { $sum: 1 } + } + }, + { $sort: { _id: 1 } }, + { + $merge: { + into: "monthlySales", + on: "_id", + whenMatched: "replace", + whenNotMatched: "insert" + } + } +]); +``` + +## Aggregation with $setWindowFields (MongoDB 5.0+) + +### Moving Averages and Rankings +```javascript +db.sales.aggregate([ + { $match: { date: { $gte: new Date("2023-01-01") } } }, + { $sort: { productId: 1, date: 1 } }, + { + $setWindowFields: { + partitionBy: "$productId", + sortBy: { date: 1 }, + output: { + movingAvg: { + $avg: "$amount", + window: { + documents: ["unbounded", "current"] + } + }, + salesRank: { + $denseRank: {} + } + } + } + } +]); +``` + +## Aggregation with $densify + +### Filling Gaps in Time Series Data +```javascript +db.sensorReadings.aggregate([ + { $match: { sensorId: "A1", timestamp: { $gte: new Date("2023-01-01") } } }, + { + $densify: { + field: "timestamp", + range: { + step: 1, + unit: "hour", + bounds: [new Date("2023-01-01"), new Date("2023-01-02")] + } + } + }, + { + $fill: { + output: { + value: { method: "linear" }, + status: { value: "interpolated" } + } + } + } +]); +``` diff --git a/docs/mongodb/crud.md b/docs/mongodb/crud.md new file mode 100644 index 0000000..605d144 --- /dev/null +++ b/docs/mongodb/crud.md @@ -0,0 +1,262 @@ +# MongoDB CRUD Operations + +## Basic CRUD Operations + +### 1. Create Operations + +#### Insert a Single Document +```javascript +// Using insertOne() +db.collection('users').insertOne({ + name: 'John Doe', + email: 'john@example.com', + age: 30, + status: 'active' +}); +``` + +#### Insert Multiple Documents +```javascript +// Using insertMany() +db.collection('users').insertMany([ + { name: 'Alice', age: 25, status: 'active' }, + { name: 'Bob', age: 35, status: 'inactive' }, + { name: 'Charlie', age: 40, status: 'pending' } +]); +``` + +### 2. Read Operations + +#### Find All Documents +```javascript +// Using find() with no query +const allUsers = await db.collection('users').find({}).toArray(); +``` + +#### Find with Query +```javascript +// Find with equality condition +const activeUsers = await db.collection('users') + .find({ status: 'active' }) + .toArray(); + +// Using comparison operators +const adultUsers = await db.collection('users') + .find({ age: { $gt: 18 } }) + .toArray(); +``` + +#### Find One Document +```javascript +const user = await db.collection('users').findOne({ email: 'john@example.com' }); +``` + +#### Projection (Selecting Fields) +```javascript +// Include only name and email fields +const users = await db.collection('users') + .find({}, { projection: { name: 1, email: 1, _id: 0 } }) + .toArray(); +``` + +#### Sorting +```javascript +// Sort by age in descending order +const sortedUsers = await db.collection('users') + .find() + .sort({ age: -1 }) + .toArray(); +``` + +#### Limiting and Skipping +```javascript +// Pagination: Get second page with 10 items per page +const page = 2; +const limit = 10; +const users = await db.collection('users') + .find() + .skip((page - 1) * limit) + .limit(limit) + .toArray(); +``` + +### 3. Update Operations + +#### Update a Single Document +```javascript +// Using updateOne() +const result = await db.collection('users').updateOne( + { email: 'john@example.com' }, + { $set: { status: 'inactive', updatedAt: new Date() } } +); + +console.log(`${result.matchedCount} document(s) matched the filter`); +console.log(`${result.modifiedCount} document(s) was/were updated`); +``` + +#### Update Multiple Documents +```javascript +// Using updateMany() +const result = await db.collection('users').updateMany( + { status: 'pending' }, + { $set: { status: 'active', updatedAt: new Date() } } +); +``` + +#### Increment a Value +```javascript +// Increment age by 1 +await db.collection('users').updateOne( + { email: 'john@example.com' }, + { $inc: { age: 1 } } +); +``` + +#### Add to Array +```javascript +// Add to array field +await db.collection('users').updateOne( + { email: 'john@example.com' }, + { $push: { tags: 'premium' } } +); + +// Add to array if not exists +await db.collection('users').updateOne( + { email: 'john@example.com' }, + { $addToSet: { tags: 'premium' } } +); +``` + +### 4. Delete Operations + +#### Delete a Single Document +```javascript +const result = await db.collection('users').deleteOne({ email: 'john@example.com' }); +console.log(`${result.deletedCount} document(s) was/were deleted`); +``` + +#### Delete Multiple Documents +```javascript +const result = await db.collection('users').deleteMany({ status: 'inactive' }); +``` + +#### Delete All Documents (but keep the collection) +```javascript +const result = await db.collection('users').deleteMany({}); +``` + +## Bulk Operations + +### Ordered Bulk Operations +```javascript +const bulk = db.collection('users').initializeOrderedBulkOp(); +bulk.insert({ name: 'User 1' }); +bulk.insert({ name: 'User 2' }); +bulk.find({ name: 'User 1' }).update({ $set: { status: 'active' } }); +const result = await bulk.execute(); +``` + +### Unordered Bulk Operations +```javascript +const bulk = db.collection('users').initializeUnorderedBulkOp(); +bulk.insert({ name: 'User 3' }); +bulk.insert({ name: 'User 4' }); +const result = await bulk.execute(); +``` + +## Find and Modify + +### Find One and Update +```javascript +const result = await db.collection('users').findOneAndUpdate( + { email: 'john@example.com' }, + { $set: { lastLogin: new Date() } }, + { returnDocument: 'after' } // Return the updated document +); +``` + +### Find One and Delete +```javascript +const result = await db.collection('users').findOneAndDelete( + { email: 'john@example.com' } +); +``` + +## Indexes + +### Create Index +```javascript +// Single field index +await db.collection('users').createIndex({ email: 1 }); + +// Compound index +await db.collection('users').createIndex({ status: 1, createdAt: -1 }); + +// Unique index +await db.collection('users').createIndex({ email: 1 }, { unique: true }); + +// Text index for text search +await db.collection('articles').createIndex({ content: 'text' }); +``` + +### List Indexes +```javascript +const indexes = await db.collection('users').indexes(); +console.log(indexes); +``` + +### Drop Index +```javascript +await db.collection('users').dropIndex('email_1'); +``` + +## Transactions (MongoDB 4.0+) + +```javascript +const session = client.startSession(); + +try { + await session.withTransaction(async () => { + // Perform operations in the transaction + const user = await db.collection('users').findOne( + { email: 'john@example.com' }, + { session } + ); + + if (!user) { + throw new Error('User not found'); + } + + await db.collection('orders').insertOne( + { + userId: user._id, + items: ['item1', 'item2'], + total: 100, + status: 'pending' + }, + { session } + ); + + await db.collection('users').updateOne( + { _id: user._id }, + { $inc: { orderCount: 1 } }, + { session } + ); + }); +} finally { + await session.endSession(); +} +``` + +## Best Practices + +1. **Use Projections** to retrieve only the fields you need +2. **Create Indexes** for frequently queried fields +3. **Use $lookup** instead of multiple queries when possible +4. **Batch Operations** for bulk inserts/updates +5. **Use Cursors** for large result sets +6. **Monitor Performance** using explain() +7. **Use Transactions** for multi-document operations that need to be atomic +8. **Set Write Concerns** appropriately for your use case +9. **Use TTL Indexes** for expiring data +10. **Regularly Compact** collections with high churn diff --git a/docs/mongodb/mongoose.md b/docs/mongodb/mongoose.md new file mode 100644 index 0000000..aaa0e5f --- /dev/null +++ b/docs/mongodb/mongoose.md @@ -0,0 +1,536 @@ +# Mongoose ODM + +## Installation +```bash +npm install mongoose +``` + +## Basic Setup +```javascript +const mongoose = require('mongoose'); + +// Connect to MongoDB +mongoose.connect('mongodb://localhost:27017/mydatabase', { + useNewUrlParser: true, + useUnifiedTopology: true +}); + +// Get the default connection +const db = mongoose.connection; + +// Event listeners +db.on('error', console.error.bind(console, 'MongoDB connection error:')); +db.once('open', () => { + console.log('Connected to MongoDB'); +}); +``` + +## Defining a Schema +```javascript +const { Schema } = mongoose; + +const userSchema = new Schema({ + username: { + type: String, + required: true, + unique: true, + trim: true, + minlength: 3 + }, + email: { + type: String, + required: true, + unique: true, + trim: true, + lowercase: true, + match: [/^\S+@\S+\.\S+$/, 'Please use a valid email address'] + }, + password: { + type: String, + required: true, + minlength: 6, + select: false // Don't return password by default + }, + role: { + type: String, + enum: ['user', 'admin'], + default: 'user' + }, + createdAt: { + type: Date, + default: Date.now + }, + updatedAt: { + type: Date, + default: Date.now + }, + profile: { + firstName: String, + lastName: String, + bio: String, + avatar: String + }, + tags: [String], + isActive: { + type: Boolean, + default: true + } +}, { + timestamps: true, // Adds createdAt and updatedAt fields + toJSON: { virtuals: true }, // Include virtuals when converting to JSON + toObject: { virtuals: true } // Include virtuals when converting to objects +}); + +// Virtual for full name +userSchema.virtual('fullName').get(function() { + return `${this.profile?.firstName || ''} ${this.profile?.lastName || ''}`.trim(); +}); + +// Indexes +userSchema.index({ email: 1 }, { unique: true }); +userSchema.index({ 'profile.firstName': 'text', 'profile.lastName': 'text' }); + +// Pre-save hook to hash password +userSchema.pre('save', async function(next) { + if (!this.isModified('password')) return next(); + + try { + const salt = await bcrypt.genSalt(10); + this.password = await bcrypt.hash(this.password, salt); + next(); + } catch (error) { + next(error); + } +}); + +// Instance method +userSchema.methods.comparePassword = async function(candidatePassword) { + return await bcrypt.compare(candidatePassword, this.password); +}; + +// Static method +userSchema.statics.findByEmail = function(email) { + return this.findOne({ email }); +}; + +// Query helper +userSchema.query.byName = function(name) { + return this.where({ name: new RegExp(name, 'i') }); +}; + +const User = mongoose.model('User', userSchema); +module.exports = User; +``` + +## CRUD Operations + +### Create +```javascript +// Create and save +const user = new User({ + username: 'johndoe', + email: 'john@example.com', + password: 'securepassword123', + role: 'user' +}); + +await user.save(); + +// Create in one step +const newUser = await User.create({ + username: 'janedoe', + email: 'jane@example.com', + password: 'securepassword456' +}); +``` + +### Read +```javascript +// Find all users +const users = await User.find({}); + +// Find one user +const user = await User.findOne({ email: 'john@example.com' }); + +// Find by ID +const user = await User.findById('507f1f77bcf86cd799439011'); + +// Find with conditions +const activeAdmins = await User.find({ + role: 'admin', + isActive: true +}).sort({ createdAt: -1 }); + +// Using query builder +const users = await User + .find({ role: 'user' }) + .where('createdAt').gt(oneYearAgo) + .sort('-createdAt') + .limit(10) + .select('username email createdAt') + .lean(); +``` + +### Update +```javascript +// Update by ID +const user = await User.findByIdAndUpdate( + '507f1f77bcf86cd799439011', + { $set: { isActive: false } }, + { new: true, runValidators: true } +); + +// Update one +await User.updateOne( + { email: 'john@example.com' }, + { $inc: { loginCount: 1 } } +); + +// Update many +await User.updateMany( + { role: 'user' }, + { $set: { lastNotified: new Date() } } +); +``` + +### Delete +```javascript +// Delete one +await User.deleteOne({ email: 'john@example.com' }); + +// Delete by ID +await User.findByIdAndDelete('507f1f77bcf86cd799439011'); + +// Delete many +await User.deleteMany({ isActive: false }); +``` + +## Middleware (Hooks) + +### Document Middleware +```javascript +userSchema.pre('save', function(next) { + this.updatedAt = new Date(); + next(); +}); + +userSchema.post('save', function(doc, next) { + console.log(`User ${doc._id} was saved`); + next(); +}); +``` + +### Query Middleware +```javascript +userSchema.pre('find', function() { + this.start = Date.now(); +}); + +userSchema.post('find', function(docs) { + console.log(`Query took ${Date.now() - this.start} ms`); +}); +``` + +### Aggregation Middleware +```javascript +userSchema.pre('aggregate', function() { + this.pipeline().unshift({ $match: { isActive: true } }); +}); +``` + +## Population (Joins) + +### Basic Population +```javascript +const postSchema = new Schema({ + title: String, + content: String, + author: { type: Schema.Types.ObjectId, ref: 'User' }, + comments: [{ type: Schema.Types.ObjectId, ref: 'Comment' }] +}); + +const Post = mongoose.model('Post', postSchema); + +// Populate author +const post = await Post.findById(postId).populate('author'); + +// Populate multiple fields +const post = await Post.findById(postId) + .populate('author') + .populate('comments'); + +// Populate with selected fields +const post = await Post.findById(postId).populate({ + path: 'author', + select: 'username email profile.firstName profile.lastName' +}); + +// Populate with conditions +const post = await Post.findById(postId).populate({ + path: 'comments', + match: { isApproved: true }, + options: { sort: { createdAt: -1 }, limit: 10 }, + select: 'content createdAt', + populate: { + path: 'author', + select: 'username' + } +}); +``` + +## Aggregation + +### Basic Aggregation +```javascript +const stats = await User.aggregate([ + { $match: { isActive: true } }, + { + $group: { + _id: '$role', + count: { $sum: 1 }, + averageAge: { $avg: '$age' } + } + }, + { $sort: { count: -1 } } +]); +``` + +### Text Search +```javascript +const results = await User.find( + { $text: { $search: 'john' } }, + { score: { $meta: 'textScore' } } +).sort({ score: { $meta: 'textScore' } }); +``` + +## Transactions + +### Basic Transaction +```javascript +const session = await mongoose.startSession(); +session.startTransaction(); + +try { + const user = await User.create([{ + username: 'newuser', + email: 'new@example.com', + password: 'password123' + }], { session }); + + await Profile.create([{ + userId: user[0]._id, + firstName: 'New', + lastName: 'User' + }], { session }); + + await session.commitTransaction(); + console.log('Transaction completed'); +} catch (error) { + await session.abortTransaction(); + console.error('Transaction aborted:', error); +} finally { + session.endSession(); +} +``` + +## Plugins + +### Creating a Plugin +```javascript +// plugins/timestamps.js +module.exports = function timestampsPlugin(schema) { + schema.add({ + createdAt: { type: Date, default: Date.now }, + updatedAt: { type: Date, default: Date.now } + }); + + schema.pre('save', function(next) { + this.updatedAt = new Date(); + next(); + }); +}; + +// Using the plugin +const timestamps = require('./plugins/timestamps'); +userSchema.plugin(timestamps); +``` + +## Validation + +### Custom Validators +```javascript +const userSchema = new Schema({ + email: { + type: String, + required: true, + validate: { + validator: function(v) { + return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v); + }, + message: props => `${props.value} is not a valid email!` + } + }, + age: { + type: Number, + min: [18, 'Must be at least 18, got {VALUE}'], + max: 120 + }, + role: { + type: String, + enum: { + values: ['user', 'admin'], + message: '{VALUE} is not a valid role' + } + } +}); + +// Async validator +userSchema.path('username').validate({ + isAsync: true, + validator: async function(value) { + const user = await this.constructor.findOne({ username: value }); + return !user || this._id.equals(user._id); + }, + message: 'Username already exists' +}); +``` + +## Error Handling + +### Handling Validation Errors +```javascript +try { + const user = await User.create({ email: 'invalid-email' }); +} catch (error) { + if (error.name === 'ValidationError') { + const errors = {}; + Object.keys(error.errors).forEach(key => { + errors[key] = error.errors[key].message; + }); + return { errors }; + } + throw error; +} +``` + +### Handling Duplicate Key Errors +```javascript +try { + const user = await User.create({ email: 'duplicate@example.com' }); +} catch (error) { + if (error.code === 11000) { + return { error: 'Email already exists' }; + } + throw error; +} +``` + +## Performance Optimization + +### Lean Queries +```javascript +// Returns plain JavaScript objects instead of Mongoose documents +const users = await User.find({}).lean(); +``` + +### Select Only Required Fields +```javascript +// Only fetch username and email +const users = await User.find({}, 'username email'); +``` + +### Use Indexes +```javascript +// Create index +userSchema.index({ email: 1 }, { unique: true }); +userSchema.index({ 'profile.location': '2dsphere' }); +``` + +### Caching +```javascript +// Simple in-memory cache +const cache = {}; + +async function getUser(id) { + if (cache[id]) { + return cache[id]; + } + + const user = await User.findById(id).cache({ key: id }); + cache[id] = user; + return user; +} +``` + +## Testing + +### Using Jest +```javascript +// __tests__/user.test.js +const mongoose = require('mongoose'); +const User = require('../models/User'); + +beforeAll(async () => { + await mongoose.connect('mongodb://localhost:27017/testdb', { + useNewUrlParser: true, + useUnifiedTopology: true + }); +}); + +afterAll(async () => { + await mongoose.connection.dropDatabase(); + await mongoose.connection.close(); +}); + +describe('User Model', () => { + it('should create a new user', async () => { + const user = await User.create({ + username: 'testuser', + email: 'test@example.com', + password: 'password123' + }); + + expect(user).toHaveProperty('_id'); + expect(user.email).toBe('test@example.com'); + }); + + it('should not create user with duplicate email', async () => { + await User.create({ + username: 'testuser1', + email: 'duplicate@example.com', + password: 'password123' + }); + + await expect( + User.create({ + username: 'testuser2', + email: 'duplicate@example.com', + password: 'password456' + }) + ).rejects.toThrow('duplicate key error'); + }); +}); +``` + +## Best Practices + +1. **Use Schemas**: Always define schemas for your models +2. **Use Middleware**: For pre/post hooks and business logic +3. **Use Virtuals**: For computed properties +4. **Use Statics**: For model-level functions +5. **Use Methods**: For document-level functions +6. **Use Plugins**: For reusable functionality +7. **Use Lean**: When you don't need Mongoose documents +8. **Use Projections**: Only fetch the fields you need +9. **Use Indexes**: For better query performance +10. **Handle Errors**: Proper error handling and validation +11. **Use Transactions**: For multi-document operations +12. **Use Environment Variables**: For configuration +13. **Use Connection Pooling**: For better performance +14. **Monitor Performance**: Use MongoDB Atlas or similar tools +15. **Keep Documents Small**: Avoid large documents (>16MB) +16. **Use Caching**: For frequently accessed data +17. **Use Aggregation**: For complex queries +18. **Use Text Search**: For full-text search capabilities +19. **Use Geospatial Queries**: For location-based data +20. **Keep Mongoose Updated**: For security and performance improvements diff --git a/docs/mongodb/schema-design.md b/docs/mongodb/schema-design.md new file mode 100644 index 0000000..95ca163 --- /dev/null +++ b/docs/mongodb/schema-design.md @@ -0,0 +1,318 @@ +# MongoDB Schema Design + +## Data Modeling Concepts + +### 1. Document Structure + +#### Embedded Documents +```javascript +// Users with embedded addresses +{ + _id: ObjectId("507f1f77bcf86cd799439011"), + name: "John Doe", + email: "john@example.com", + addresses: [ + { + type: "home", + street: "123 Main St", + city: "Springfield", + zip: "12345", + isPrimary: true + }, + { + type: "work", + street: "456 Work Ave", + city: "Springfield", + zip: "12345" + } + ] +} +``` + +#### Referenced Documents +```javascript +// Users collection +{ + _id: ObjectId("507f1f77bcf86cd799439011"), + name: "John Doe", + email: "john@example.com" +} + +// Addresses collection +{ + _id: ObjectId("5a934e000102030405000000"), + userId: ObjectId("507f1f77bcf86cd799439011"), + type: "home", + street: "123 Main St", + city: "Springfield", + zip: "12345", + isPrimary: true +} +``` + +## Schema Design Patterns + +### 1. Embedding (Denormalization) +**When to use:** +- One-to-few relationships +- Data that's always accessed together +- Data that changes together + +**Example: Blog Post with Comments** +```javascript +{ + _id: ObjectId("5a934e000102030405000001"), + title: "MongoDB Schema Design", + content: "...", + author: "John Doe", + publishedAt: ISODate("2023-01-15T10:00:00Z"), + comments: [ + { + _id: ObjectId("5a934e000102030405000002"), + author: "Alice", + text: "Great post!", + createdAt: ISODate("2023-01-15T11:30:00Z") + }, + { + _id: ObjectId("5a934e000102030405000003"), + author: "Bob", + text: "Thanks for sharing!", + createdAt: ISODate("2023-01-16T09:15:00Z") + } + ] +} +``` + +### 2. Referencing (Normalization) +**When to use:** +- One-to-many or many-to-many relationships +- Large documents that exceed 16MB +- Frequently updated data + +**Example: Products and Categories** +```javascript +// Products collection +{ + _id: ObjectId("5a934e000102030405000100"), + name: "Laptop", + price: 999.99, + categoryIds: [ + ObjectId("5a934e000102030405000200"), // Electronics + ObjectId("5a934e000102030405000201") // Computers + ] +} + +// Categories collection +{ + _id: ObjectId("5a934e000102030405000200"), + name: "Electronics", + description: "Electronic devices" +} + +{ + _id: ObjectId("5a934e000102030405000201"), + name: "Computers", + description: "Computers and laptops" +} +``` + +### 3. Bucket Pattern +**When to use:** +- Time-series data +- IoT sensor data +- Logging + +**Example: Sensor Data** +```javascript +{ + _id: "sensor-1-2023-01", + sensorId: "sensor-1", + type: "temperature", + unit: "Celsius", + measurements: [ + { timestamp: ISODate("2023-01-01T00:00:00Z"), value: 21.5 }, + { timestamp: ISODate("2023-01-01T00:05:00Z"), value: 21.7 }, + // ... more measurements for the month + ], + metadata: { + location: "Room 101", + min: 18.0, + max: 25.0 + } +} +``` + +### 4. Attribute Pattern +**When to use:** +- Heterogeneous document attributes +- Sparse fields +- Polymorphic schemas + +**Example: E-commerce Product Catalog** +```javascript +{ + _id: ObjectId("5a934e000102030405000300"), + name: "Smartphone X", + category: "electronics", + attributes: [ + { name: "color", value: "black" }, + { name: "storage", value: "128GB" }, + { name: "ram", value: "8GB" } + ] +} + +{ + _id: ObjectId("5a934e000102030405000301"), + name: "T-Shirt", + category: "clothing", + attributes: [ + { name: "color", value: "blue" }, + { name: "size", value: "L" }, + { name: "material", value: "cotton" } + ] +} +``` + +## Schema Validation + +### JSON Schema Validation +```javascript +db.createCollection("users", { + validator: { + $jsonSchema: { + bsonType: "object", + required: ["name", "email", "status"], + properties: { + name: { + bsonType: "string", + description: "must be a string and is required" + }, + email: { + bsonType: "string", + pattern: "^.+@.+\\.", + description: "must be a valid email and is required" + }, + status: { + enum: ["active", "inactive", "pending"], + description: "must be a valid status and is required" + }, + age: { + bsonType: ["int", "null"], + minimum: 0, + maximum: 120, + description: "must be an integer between 0 and 120" + } + } + } + } +}); +``` + +## Data Types + +### Common Data Types +- String (`string`) +- Number (`int32`, `int64`, `double`) +- Boolean (`bool`) +- Date (`date`) +- Array (`array`) +- Object (`object`) +- ObjectId (`objectId`) +- Binary Data (`binData`) +- Null (`null`) + +### Special Types +- Decimal128 (for precise decimal arithmetic) +- Timestamp (for internal MongoDB use) +- Regular Expression (`regex`) +- JavaScript (`javascript`) +- MinKey/MaxKey (for comparison operations) + +## Indexing Strategies + +### Single Field Index +```javascript +db.users.createIndex({ email: 1 }); +``` + +### Compound Index +```javascript +db.orders.createIndex({ customerId: 1, orderDate: -1 }); +``` + +### Multikey Index (for arrays) +```javascript +db.products.createIndex({ tags: 1 }); +``` + +### Text Index +```javascript +db.articles.createIndex({ title: "text", content: "text" }); +``` + +### Wildcard Index +```javascript +db.users.createIndex({ "metadata.$**": 1 }); +``` + +## Sharding Strategies + +### Hashed Sharding +```javascript +sh.shardCollection("mydb.users", { _id: "hashed" }); +``` + +### Ranged Sharding +```javascript +sh.shardCollection("mydb.orders", { customerId: 1 }); +``` + +### Zoned Sharding +```javascript +sh.addShardTag("shard0000", "US"); +sh.addShardTag("shard0001", "EU"); +sh.addTagRange("mydb.users", { country: "US" }, { country: "US" }, "US"); +sh.addTagRange("mydb.users", { country: "UK" }, { country: "FR" }, "EU"); +``` + +## Data Lifecycle Management + +### TTL Indexes +```javascript +// Documents will be automatically deleted after 30 days +db.logs.createIndex({ createdAt: 1 }, { expireAfterSeconds: 2592000 }); +``` + +### Capped Collections +```javascript +// Create a capped collection of 1MB +db.createCollection("recent_activities", { capped: true, size: 1048576 }); +``` + +## Schema Evolution + +### Adding Fields +```javascript +db.users.updateMany( + { newField: { $exists: false } }, + { $set: { newField: "default value" } } +); +``` + +### Migrating Data +```javascript +// Add new structure +db.products.updateMany( + {}, + { $set: { "metadata.tags": [] } } +); + +// Migrate old tags to new structure +db.products.updateMany( + { tags: { $exists: true } }, + [ + { $set: { "metadata.tags": "$tags" } }, + { $unset: "tags" } + ] +); +``` diff --git a/docs/mongodb/setup.md b/docs/mongodb/setup.md new file mode 100644 index 0000000..f098700 --- /dev/null +++ b/docs/mongodb/setup.md @@ -0,0 +1,211 @@ +# MongoDB Setup Guide + +## Installation + +### macOS (using Homebrew) +```bash +# Install MongoDB Community Edition +brew tap mongodb/brew +brew install mongodb-community + +# Start MongoDB +brew services start mongodb-community + +# Verify installation +mongod --version +mongo --version +``` + +### Windows +1. Download MongoDB Community Server from [MongoDB Download Center](https://www.mongodb.com/try/download/community) +2. Run the installer and follow the prompts +3. Add MongoDB's `bin` directory to your system's PATH +4. Create a data directory: `C:\data\db` +5. Start MongoDB: `mongod` + +### Linux (Ubuntu/Debian) +```bash +# Import the public key +wget -qO - https://www.mongodb.org/static/pgp/server-5.0.asc | sudo apt-key add - + +# Create list file +echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/5.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-5.0.list + +# Reload local package database +sudo apt-get update + +# Install MongoDB +sudo apt-get install -y mongodb-org + +# Start MongoDB +sudo systemctl start mongod + +# Enable automatic startup +sudo systemctl enable mongod +``` + +## MongoDB Compass (GUI Tool) +Download and install MongoDB Compass from: https://www.mongodb.com/try/download/compass + +## Basic Commands + +### Start MongoDB Shell +```bash +mongosh # For MongoDB 6.0+ +# or +mongo # For older versions +``` + +### Show Databases +```javascript +show dbs +``` + +### Switch/Create Database +```javascript +use mydb +``` + +### Show Collections +```javascript +show collections +``` + +### Get Help +```javascript +db.help() +``` + +## Connecting with Node.js + +### Install MongoDB Driver +```bash +npm install mongodb +``` + +### Basic Connection +```javascript +const { MongoClient } = require('mongodb'); + +// Connection URI +const uri = 'mongodb://localhost:27017'; + +// Create a new MongoClient +const client = new MongoClient(uri); + +async function run() { + try { + // Connect the client to the server + await client.connect(); + console.log('Connected successfully to MongoDB'); + + // Get the database + const db = client.db('mydb'); + + // Get the collection + const collection = db.collection('documents'); + + // Insert a document + await collection.insertOne({ name: 'Test', value: 1 }); + + // Find documents + const docs = await collection.find({}).toArray(); + console.log('Found documents:', docs); + + } finally { + // Close the connection + await client.close(); + } +} + +run().catch(console.dir); +``` + +## Configuration + +### MongoDB Configuration File (mongod.conf) +Typical location: +- Linux: `/etc/mongod.conf` +- macOS: `/usr/local/etc/mongod.conf` +- Windows: `C:\Program Files\MongoDB\Server\\bin\mongod.cfg` + +### Common Configuration Options +```yaml +# Network interfaces +net: + port: 27017 + bindIp: 127.0.0.1 # Only localhost + +# Storage +dbPath: /data/db + +# Security +security: + authorization: enabled # Enable authentication + +# Replication (for replica sets) +replication: + replSetName: "rs0" + +# Performance +storage: + journal: + enabled: true + wiredTiger: + engineConfig: + cacheSizeGB: 1 # Adjust based on available RAM +``` + +## Authentication + +### Create Admin User +```javascript +use admin +db.createUser({ + user: 'admin', + pwd: 'your-secure-password', + roles: [ { role: 'userAdminAnyDatabase', db: 'admin' } ] +}) +``` + +### Connect with Authentication +```javascript +const uri = 'mongodb://admin:your-secure-password@localhost:27017/admin'; +const client = new MongoClient(uri); +``` + +## Backup and Restore + +### Create Backup +```bash +mongodump --uri="mongodb://localhost:27017" --out=/backup/$(date +%Y%m%d) +``` + +### Restore from Backup +```bash +mongorestore --uri="mongodb://localhost:27017" /backup/20231015/mydb +``` + +## Monitoring + +### Basic Stats +```javascript +db.serverStatus() + +db.stats() + +// Collection stats +db.collection.stats() + +// Current operations +db.currentOp() +``` + +### Enable Profiling +```javascript +// Set profiling level (0=off, 1=slow, 2=all) +db.setProfilingLevel(1, { slowms: 100 }) + +// View profile data +db.system.profile.find().pretty() +``` From 9891ea4171a58f82c489d7bd9e4e2471cdecd6a9 Mon Sep 17 00:00:00 2001 From: Divya Pahuja Date: Thu, 16 Oct 2025 00:25:44 +0530 Subject: [PATCH 3/6] adding css tricks --- docs/ui/css-tricks.md | 581 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 581 insertions(+) create mode 100644 docs/ui/css-tricks.md diff --git a/docs/ui/css-tricks.md b/docs/ui/css-tricks.md new file mode 100644 index 0000000..4be1bf7 --- /dev/null +++ b/docs/ui/css-tricks.md @@ -0,0 +1,581 @@ +# CSS Tricks and Tips + +## Layout + +### Flexbox Centering +Center elements both horizontally and vertically: +```css +.container { + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; /* Optional: for full viewport height */ +} +``` + +### CSS Grid Layout +Create a responsive grid with auto-fill: +```css +.grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: 1rem; +} +``` + +### Sticky Footer +Keep footer at the bottom of the page: +```css +body { + display: flex; + flex-direction: column; + min-height: 100vh; +} + +main { + flex: 1; +} +``` + +## Effects + +### Smooth Scrolling +```css +html { + scroll-behavior: smooth; +} +``` + +### Custom Scrollbar +```css +/* WebKit (Chrome, Safari, newer Edge) */ +::-webkit-scrollbar { + width: 10px; +} + +::-webkit-scrollbar-track { + background: #f1f1f1; +} + +::-webkit-scrollbar-thumb { + background: #888; + border-radius: 5px; +} + +/* Firefox */ +html { + scrollbar-width: thin; + scrollbar-color: #888 #f1f1f1; +} +``` + +### Hover Effects +```css +.button { + transition: transform 0.3s ease, box-shadow 0.3s ease; +} + +.button:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} +``` + +## Typography + +### Text Overflow Ellipsis +```css +.ellipsis { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; +} + +/* Multi-line ellipsis (3 lines) */ +.multi-line-ellipsis { + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} +``` + +### System Font Stack +```css +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + "Helvetica Neue", Arial, sans-serif; +} +``` + +## Responsive Design + +### Mobile-First Media Queries +```css +/* Base styles (mobile) */ +.container { + padding: 1rem; +} + +/* Tablet */ +@media (min-width: 768px) { + .container { + padding: 2rem; + } +} + +/* Desktop */ +@media (min-width: 1024px) { + .container { + max-width: 1200px; + margin: 0 auto; + } +} +``` + +### Responsive Images +```css +.responsive-img { + max-width: 100%; + height: auto; + display: block; +} + +/* Art direction with picture element */ + + + + Description + +``` + +## Animations + +### Keyframe Animation +```css +@keyframes fadeIn { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } +} + +.animate-fade-in { + animation: fadeIn 0.6s ease-out forwards; +} +``` + +### Hover Underline Animation +```css +.hover-underline { + position: relative; + text-decoration: none; +} + +.hover-underline::after { + content: ''; + position: absolute; + width: 0; + height: 2px; + bottom: -2px; + left: 0; + background-color: currentColor; + transition: width 0.3s ease; +} + +.hover-underline:hover::after { + width: 100%; +} +``` + +## Forms + +### Custom Checkbox/Radio +```css +.custom-checkbox { + position: relative; + padding-left: 30px; + cursor: pointer; +} + +.custom-checkbox input { + position: absolute; + opacity: 0; + cursor: pointer; + height: 0; + width: 0; +} + +.checkmark { + position: absolute; + top: 0; + left: 0; + height: 20px; + width: 20px; + background-color: #eee; + border: 2px solid #ddd; + border-radius: 4px; +} + +.custom-checkbox:hover input ~ .checkmark { + background-color: #ccc; +} + +.custom-checkbox input:checked ~ .checkmark { + background-color: #2196F3; + border-color: #2196F3; +} + +.checkmark:after { + content: ""; + position: absolute; + display: none; +} + +.custom-checkbox input:checked ~ .checkmark:after { + display: block; +} + +.custom-checkbox .checkmark:after { + left: 6px; + top: 2px; + width: 5px; + height: 10px; + border: solid white; + border-width: 0 2px 2px 0; + transform: rotate(45deg); +} +``` + +## Variables and Theming + +### CSS Custom Properties +```css +:root { + --primary-color: #3498db; + --secondary-color: #2ecc71; + --spacing-unit: 1rem; + --border-radius: 4px; + --transition: all 0.3s ease; +} + +.button { + background-color: var(--primary-color); + padding: calc(var(--spacing-unit) * 2); + border-radius: var(--border-radius); + transition: var(--transition); +} + +/* Dark mode theming */ +@media (prefers-color-scheme: dark) { + :root { + --primary-color: #2980b9; + --secondary-color: #27ae60; + } +} +``` + +## Performance + +### Will-Change Property +```css +.optimize { + will-change: transform, opacity; +} +``` + +### Content Visibility +```css +.long-content { + content-visibility: auto; + contain-intrinsic-size: 0 500px; /* Estimated height */ +} +``` + +## Browser Compatibility + +### Feature Queries +```css +@supports (display: grid) { + .container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + } +} + +@supports not (display: grid) { + .container { + display: flex; + flex-wrap: wrap; + } + + .container > * { + flex: 1 1 250px; + margin: 0.5rem; + } +} +``` + +### Vendor Prefixes +```css +.gradient-bg { + background: #1e5799; /* Fallback */ + background: -moz-linear-gradient(top, #1e5799 0%, #2989d8 100%); + background: -webkit-linear-gradient(top, #1e5799 0%,#2989d8 100%); + background: linear-gradient(to bottom, #1e5799 0%,#2989d8 100%); + filter: progid:DXImageTransform.Microsoft.gradient( + startColorstr='#1e5799', + endColorstr='#2989d8', + GradientType=0 + ); +} +``` + +## Debugging + +### Debugging Layout +```css +/* Add to any element to debug */ +.debug { + outline: 1px solid red; + background: rgba(255, 0, 0, 0.1); +} + +/* Debug all elements */ +* { outline: 1px solid rgba(255, 0, 0, 0.2); } +* * { outline: 1px solid rgba(0, 255, 0, 0.2); } +* * * { outline: 1px solid rgba(0, 0, 255, 0.2); } +* * * * { outline: 1px solid rgba(255, 0, 255, 0.2); } +* * * * * { outline: 1px solid rgba(0, 255, 255, 0.2); } +``` + +### Print Styles +```css +@media print { + /* Hide elements when printing */ + .no-print { + display: none !important; + } + + /* Page breaks */ + .page-break { + page-break-before: always; + } + + /* Prevent text from breaking across pages */ + p, h1, h2, h3, h4, h5, h6 { + page-break-inside: avoid; + } + + /* Set page margins */ + @page { + margin: 2cm; + } +} +``` + +## Modern CSS + +### Aspect Ratio +```css +.aspect-ratio-box { + aspect-ratio: 16 / 9; + background: #f0f0f0; +} +``` + +### CSS Grid Subgrid +```css +.grid-container { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1rem; +} + +.grid-item { + display: grid; + grid-template-rows: subgrid; + grid-row: span 3; /* Number of rows this item spans */ +} +``` + +### Container Queries +```css +.component { + container-type: inline-size; +} + +@container (min-width: 400px) { + .component .content { + display: flex; + gap: 1rem; + } +} +``` + +## Accessibility + +### Screen Reader Only +```css +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + +/* For interactive elements that need to be visible on focus */ +.sr-only.focusable:focus { + position: static; + width: auto; + height: auto; + padding: 0.5em; + margin: 0; + overflow: visible; + clip: auto; + white-space: normal; +} +``` + +### Reduced Motion +```css +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} +``` + +## Bonus: CSS Reset +```css +/* Box sizing rules */ +*, +*::before, +*::after { + box-sizing: border-box; +} + +/* Remove default margin and padding */ +html, +body, +h1, h2, h3, h4, h5, h6, +p, blockquote, pre, +dl, dd, ol, ul, +figure, +fieldset, legend, +textarea, +pre, iframe, +hr { + margin: 0; + padding: 0; +} + +/* Set core body defaults */ +body { + min-height: 100vh; + scroll-behavior: smooth; + text-rendering: optimizeSpeed; + line-height: 1.5; +} + +/* Remove list styles on ul, ol elements */ +ul[class], +ol[class] { + list-style: none; +} + +/* Make images easier to work with */ +img, +picture, +video, +canvas, +svg { + max-width: 100%; + display: block; +} + +/* Inherit fonts for inputs and buttons */ +input, +button, +textarea, +select { + font: inherit; +} + +/* Remove all animations and transitions for people that prefer not to see them */ +@media (prefers-reduced-motion: reduce) { + * { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} +``` + +## CSS Variables for Theming +```css +:root { + /* Colors */ + --color-primary: #2563eb; + --color-secondary: #7c3aed; + --color-success: #10b981; + --color-warning: #f59e0b; + --color-danger: #ef4444; + + /* Typography */ + --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + --font-mono: 'Fira Code', 'Courier New', monospace; + + /* Spacing */ + --space-xs: 0.25rem; + --space-sm: 0.5rem; + --space-md: 1rem; + --space-lg: 2rem; + --space-xl: 4rem; + + /* Border radius */ + --radius-sm: 0.25rem; + --radius-md: 0.5rem; + --radius-lg: 1rem; + --radius-full: 9999px; + + /* Shadows */ + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + --shadow-md: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + --shadow-lg: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + + /* Transitions */ + --transition-fast: 150ms ease; + --transition-normal: 300ms ease; + --transition-slow: 500ms ease; +} + +/* Dark mode */ +@media (prefers-color-scheme: dark) { + :root { + --color-primary: #3b82f6; + --color-secondary: #8b5cf6; + --color-success: #10b981; + --color-warning: #f59e0b; + --color-danger: #ef4444; + + --shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.3); + --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2); + --shadow-md: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2); + --shadow-lg: 0 20px 25px -5px rgba(0, 0, 0, 0.3), 0 10px 10px -5px rgba(0, 0, 0, 0.2); + } +} +``` + +This CSS tricks guide covers a wide range of techniques from basic to advanced, including layout, animations, responsive design, accessibility, and modern CSS features. Each example is ready to use and includes comments for better understanding. From 7201fb8c249c8d804b6930537e04178d1418740b Mon Sep 17 00:00:00 2001 From: Divya Pahuja Date: Thu, 16 Oct 2025 00:31:24 +0530 Subject: [PATCH 4/6] adding shadcn ui docs --- docs/ui/shadcn.md | 580 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 580 insertions(+) create mode 100644 docs/ui/shadcn.md diff --git a/docs/ui/shadcn.md b/docs/ui/shadcn.md new file mode 100644 index 0000000..8249a94 --- /dev/null +++ b/docs/ui/shadcn.md @@ -0,0 +1,580 @@ +# shadcn/ui Documentation + +## Introduction + +shadcn/ui is a collection of beautifully designed, accessible, and customizable React components that you can copy and paste into your apps. It's built on top of Radix UI and Tailwind CSS. + +## Installation + +### 1. Create a new Next.js project +```bash +npx create-next-app@latest my-app --typescript --tailwind --eslint +cd my-app +``` + +### 2. Install shadcn/ui +```bash +npx shadcn-ui@latest init +``` + +### 3. Configure your `components.json` +```json +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "app/globals.css", + "baseColor": "zinc", + "cssVariables": true + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + } +} +``` + +## Adding Components + +### Add a component +```bash +npx shadcn-ui@latest add button +npx shadcn-ui@latest add dropdown-menu +npx shadcn-ui@latest add card +``` + +### Available Components +- `accordion` +- `alert` +- `alert-dialog` +- `aspect-ratio` +- `avatar` +- `badge` +- `button` +- `calendar` +- `card` +- `checkbox` +- `collapsible` +- `command` +- `context-menu` +- `dialog` +- `dropdown-menu` +- `form` +- `hover-card` +- `input` +- `label` +- `menubar` +- `navigation-menu` +- `popover` +- `progress` +- `radio-group` +- `scroll-area` +- `select` +- `separator` +- `sheet` +- `skeleton` +- `slider` +- `switch` +- `table` +- `tabs` +- `textarea` +- `toast` +- `toaster` +- `toggle` +- `tooltip` + +## Basic Usage + +### Button Component +```tsx +import { Button } from "@/components/ui/button" + +export function ButtonDemo() { + return ( + + ) +} +``` + +### Card Component +```tsx +import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@/components/ui/card" +import { Button } from "@/components/ui/button" + +export function CardDemo() { + return ( + + + Create project + Deploy your new project in one-click. + + +
+
+ {/* Form fields go here */} +
+
+
+ + + + +
+ ) +} +``` + +## Form Components + +### Form with Validation +```tsx +"use client" + +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm } from "react-hook-form" +import * as z from "zod" + +import { Button } from "@/components/ui/button" +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form" +import { Input } from "@/components/ui/input" + +const formSchema = z.object({ + username: z.string().min(2, { + message: "Username must be at least 2 characters.", + }), + email: z.string().email({ + message: "Please enter a valid email address.", + }), +}) + +export function ProfileForm() { + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + username: "", + email: "", + }, + }) + + function onSubmit(values: z.infer) { + console.log(values) + } + + return ( +
+ + ( + + Username + + + + + This is your public display name. + + + + )} + /> + ( + + Email + + + + + + )} + /> + + + + ) +} +``` + +## Dark Mode + +### Theme Provider +```tsx +// app/providers.tsx +"use client" + +import * as React from "react" +import { ThemeProvider as NextThemesProvider } from "next-themes" +import { type ThemeProviderProps } from "next-themes/dist/types" + +export function ThemeProvider({ children, ...props }: ThemeProviderProps) { + return {children} +} + +// app/layout.tsx +import { ThemeProvider } from "@/app/providers" + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + + {children} + + + + ) +} + +// components/theme-toggle.tsx +"use client" + +import { Moon, Sun } from "lucide-react" +import { useTheme } from "next-themes" + +import { Button } from "@/components/ui/button" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" + +export function ModeToggle() { + const { setTheme } = useTheme() + + return ( + + + + + + setTheme("light")}> + Light + + setTheme("dark")}> + Dark + + setTheme("system")}> + System + + + + ) +} +``` + +## Customization + +### Customizing Theme +```ts +// tailwind.config.js +const { fontFamily } = require('tailwindcss/defaultTheme') + +/** @type {import('tailwindcss').Config} */ +module.exports = { + darkMode: ["class"], + theme: { + container: { + center: true, + padding: "2rem", + screens: { + "2xl": "1400px", + }, + }, + extend: { + colors: { + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + popover: { + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))", + }, + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + }, + borderRadius: { + lg: `var(--radius)`, + md: `calc(var(--radius) - 2px)`, + sm: "calc(var(--radius) - 4px)", + }, + fontFamily: { + sans: ["var(--font-sans)", ...fontFamily.sans], + }, + keyframes: { + "accordion-down": { + from: { height: 0 }, + to: { height: "var(--radix-accordion-content-height)" }, + }, + "accordion-up": { + from: { height: "var(--radix-accordion-content-height)" }, + to: { height: 0 }, + }, + }, + animation: { + "accordion-down": "accordion-down 0.2s ease-out", + "accordion-up": "accordion-up 0.2s ease-out", + }, + }, + }, + plugins: [require("tailwindcss-animate")], +} +``` + +## Advanced Usage + +### Custom Component with Variants +```tsx +import { cva, type VariantProps } from "class-variance-authority" +import { Loader2 } from "lucide-react" +import * as React from "react" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "underline-offset-4 hover:underline text-primary", + }, + size: { + default: "h-10 py-2 px-4", + sm: "h-9 px-3 rounded-md", + lg: "h-11 px-8 rounded-md", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + isLoading?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, isLoading, children, ...props }, ref) => { + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } +``` + +## Best Practices + +1. **Component Composition** + - Compose smaller components to build complex UIs + - Use the `asChild` prop for flexible component composition + +2. **Performance** + - Use dynamic imports for large components + - Implement proper loading states + - Use `React.memo` for expensive components + +3. **Accessibility** + - All components are built with accessibility in mind + - Use proper ARIA attributes + - Ensure keyboard navigation works + +4. **Theming** + - Use CSS variables for theming + - Support both light and dark modes + - Provide proper contrast ratios + +## Common Patterns + +### Data Table with Sorting +```tsx +"use client" + +import { + ColumnDef, + ColumnFiltersState, + SortingState, + VisibilityState, + flexRender, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, +} from "@tanstack/react-table" + +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table" + +interface DataTableProps { + columns: ColumnDef[] + data: TData[] +} + +export function DataTable({ + columns, + data, +}: DataTableProps) { + const [sorting, setSorting] = React.useState([]) + const [columnFilters, setColumnFilters] = React.useState([]) + const [columnVisibility, setColumnVisibility] = React.useState({}) + const [rowSelection, setRowSelection] = React.useState({}) + + const table = useReactTable({ + data, + columns, + state: { + sorting, + columnFilters, + columnVisibility, + rowSelection, + }, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + onColumnVisibilityChange: setColumnVisibility, + onRowSelectionChange: setRowSelection, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + getFilteredRowModel: getFilteredRowModel(), + }) + + return ( +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ) + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + ) : ( + + + No results. + + + )} + +
+
+ ) +} +``` + +## Resources + +- [Official Documentation](https://ui.shadcn.com/) +- [GitHub Repository](https://github.com/shadcn/ui) +- [Radix UI Primitives](https://www.radix-ui.com/) +- [Tailwind CSS](https://tailwindcss.com/) +- [Class Variance Authority](https://github.com/joe-bell/cva) From 9ab602d911e67ff7323cf697fbc3876b5f0ff1b1 Mon Sep 17 00:00:00 2001 From: Divya Pahuja Date: Thu, 16 Oct 2025 00:36:21 +0530 Subject: [PATCH 5/6] adding bootstrap docs --- docs/ui/bootstrap.md | 529 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 529 insertions(+) create mode 100644 docs/ui/bootstrap.md diff --git a/docs/ui/bootstrap.md b/docs/ui/bootstrap.md new file mode 100644 index 0000000..9d00bc8 --- /dev/null +++ b/docs/ui/bootstrap.md @@ -0,0 +1,529 @@ +# Bootstrap 5 Documentation + +## Table of Contents +1. [Getting Started](#getting-started) +2. [Layout](#layout) +3. [Components](#components) +4. [Utilities](#utilities) +5. [Forms](#forms) +6. [Icons](#icons) +7. [Theming](#theming) +8. [Customization](#customization) +9. [Best Practices](#best-practices) + +## Getting Started + +### Installation + +#### CDN (Quick Start) +```html + + + + + +``` + +#### NPM +```bash +npm install bootstrap@5.3.0 +``` + +#### Basic HTML Template +```html + + + + + + Bootstrap 5 + + + +

Hello, Bootstrap 5!

+ + + +``` + +## Layout + +### Container +```html +
+ +
+ +
+ +
+ + +
100% wide until small breakpoint
+
100% wide until medium breakpoint
+
100% wide until large breakpoint
+
100% wide until extra large breakpoint
+
100% wide until extra extra large breakpoint
+``` + +### Grid System +```html +
+
+
Column 1
+
Column 2
+
Column 3
+
+ +
+
Main content (8/12)
+
Sidebar (4/12)
+
+ + +
+
+ +
+
+
+``` + +### Breakpoints +| Breakpoint | Class Infix | Dimensions | +|-------------|------------|------------| +| X-Small | None | <576px | +| Small | `sm` | ≥576px | +| Medium | `md` | ≥768px | +| Large | `lg` | ≥992px | +| Extra large | `xl` | ≥1200px | +| Extra extra large | `xxl` | ≥1400px | + +## Components + +### Buttons +```html + + + + + + + + +
+ + + +
+``` + +### Cards +```html +
+ ... +
+
Card title
+

Some quick example text to build on the card title and make up the bulk of the card's content.

+ Go somewhere +
+
+ + +
+
+ Featured +
+
+
Special title treatment
+

With supporting text below as a natural lead-in to additional content.

+ Go somewhere +
+ +
+``` + +### Navbar +```html + +``` + +### Modal +```html + + + + + +``` + +## Utilities + +### Spacing +```html + +
Margin all around (0.25rem)
+
Margin top (1rem)
+
Padding all around (1.5rem)
+
Padding left and right (3rem)
+ + +
Responsive margin
+``` + +### Text +```html +

Start aligned text

+

Center aligned text

+

End aligned text

+ +

Primary text

+

Success text

+

Danger text

+ +

Bold text

+

Italic text

+

Uppercase text

+

Truncated text with an ellipsis...

+``` + +### Colors +```html + +

.text-primary

+

.text-secondary

+

.text-success

+

.text-danger

+

.text-warning

+

.text-info

+

.text-light

+

.text-dark

+

.text-muted

+ + +
.bg-primary
+
.bg-success
+``` + +## Forms + +### Basic Form +```html +
+
+ + +
We'll never share your email with anyone else.
+
+
+ + +
+
+ + +
+ +
+``` + +### Form Controls +```html + + + + +
+ + +
+ + +
+ + +
+ + + + +``` + +### Form Validation +```html +
+
+ + +
+ Looks good! +
+
+
+ + +
+ Looks good! +
+
+
+ +
+
+``` + +## Icons + +### Bootstrap Icons +```html + + + + + + +``` + +## Theming + +### Color Modes +```html + + + + + + +``` + +### Custom Colors +```scss +// Custom.scss +// Override default variables before the import +$body-bg: #000; +$body-color: #fff; +$primary: #0d6efd; +$secondary: #6c757d; +$success: #198754; + +// Import Bootstrap and its default variables +@import '~bootstrap/scss/bootstrap'; +``` + +## Customization + +### SASS Variables +```scss +// Customize colors +$primary: #0074d9; +$secondary: #6c757d; +$success: #28a745; +$info: #17a2b8; +$warning: #ffc107; +$danger: #dc3545; +$light: #f8f9fa; +$dark: #343a40; + +// Customize spacing +$spacer: 1rem; +$spacers: ( + 0: 0, + 1: $spacer * .25, + 2: $spacer * .5, + 3: $spacer, + 4: $spacer * 1.5, + 5: $spacer * 3, + 6: $spacer * 4, + 7: $spacer * 5, + 8: $spacer * 6, +); + +// Customize container sizes +$container-max-widths: ( + sm: 540px, + md: 720px, + lg: 960px, + xl: 1140px, + xxl: 1320px +); +``` + +## Best Practices + +### Responsive Images +```html +Responsive image +Thumbnail +... +``` + +### Display Property +```html +
d-inline
+
d-inline
+ +d-block +d-block + + +
Hide on screens wider than lg
+
Hide on screens smaller than lg
+``` + +### Flexbox +```html +
I'm a flexbox container!
+ +
...
+
...
+
...
+
...
+
...
+ +
...
+
...
+
...
+``` + +### Visibility +```html +
...
+ + + +
Visible on all viewport sizes
+
Visible on medium and larger viewports
+
Hidden on medium and larger viewports
+``` + +## JavaScript Components + +### Tooltips +```html + + + + +``` + +### Popovers +```html + + + + +``` + +### Carousel +```html + +``` + +## Resources + +- [Official Documentation](https://getbootstrap.com/docs/5.3/getting-started/introduction/) +- [Bootstrap Icons](https://icons.getbootstrap.com/) +- [Bootstrap Examples](https://getbootstrap.com/docs/5.3/examples/) +- [GitHub Repository](https://github.com/twbs/bootstrap) +- [Bootstrap Blog](https://blog.getbootstrap.com/) From 00468c6d33b0db1b2c27bae210b0d5ae2cef65a1 Mon Sep 17 00:00:00 2001 From: Divya Pahuja Date: Thu, 16 Oct 2025 00:46:41 +0530 Subject: [PATCH 6/6] adding docs for vue --- docs/vue/components.md | 1468 ++++++++++++++++++++++++++++++++++++++++ docs/vue/reactivity.md | 533 +++++++++++++++ docs/vue/routing.md | 1084 +++++++++++++++++++++++++++++ docs/vue/setup.md | 323 +++++++++ docs/vue/vuex.md | 502 ++++++++++++++ 5 files changed, 3910 insertions(+) create mode 100644 docs/vue/components.md create mode 100644 docs/vue/reactivity.md create mode 100644 docs/vue/routing.md create mode 100644 docs/vue/setup.md create mode 100644 docs/vue/vuex.md diff --git a/docs/vue/components.md b/docs/vue/components.md new file mode 100644 index 0000000..823b11c --- /dev/null +++ b/docs/vue/components.md @@ -0,0 +1,1468 @@ +# Vue Components + +## Table of Contents +1. [Component Basics](#component-basics) +2. [Component Registration](#component-registration) +3. [Props](#props) +4. [Emits](#emits) +5. [Slots](#slots) +6. [Provide/Inject](#provide--inject) +7. [Dynamic Components](#dynamic-components) +8. [Async Components](#async-components) +9. [Composables](#composables) +10. [Render Functions & JSX](#render-functions--jsx) +11. [Custom Directives](#custom-directives) +12. [Plugins](#plugins) +13. [Performance Optimization](#performance-optimization) + +## Component Basics + +### Single File Components (SFC) +```vue + + + + + +``` + +### Options API (Legacy) +```vue + + + + + +``` + +## Component Registration + +### Global Registration +```javascript +// main.js +import { createApp } from 'vue' +import App from './App.vue' +import MyComponent from './components/MyComponent.vue' + +const app = createApp(App) + +// Register globally +app.component('MyComponent', MyComponent) + +app.mount('#app') +``` + +### Local Registration +```vue + + + +``` + +### Auto-import Components +```javascript +// plugins/components.js +import { defineAsyncComponent } from 'vue' + +export default { + install(app) { + const components = import.meta.glob('./components/**/*.vue') + + for (const [path, component] of Object.entries(components)) { + const componentName = path + .split('/') + .pop() + .replace(/\.\w+$/, '') + + app.component(componentName, defineAsyncComponent(component)) + } + } +} + +// main.js +import { createApp } from 'vue' +import App from './App.vue' +import components from './plugins/components' + +const app = createApp(App) +app.use(components) +app.mount('#app') +``` + +## Props + +### Prop Types and Validation +```vue + +``` + +### One-Way Data Flow +```vue + + + +``` + +## Emits + +### Declaring Emits +```vue + +``` + +### v-model with Components +```vue + + + + + + + +``` + +### Multiple v-model Bindings +```vue + + + + + + + +``` + +## Slots + +### Basic Slots +```vue + + + + + + + +

Main content goes here

+ + +
+``` + +### Scoped Slots +```vue + + + + + + + +``` + +### Renderless Components +```vue + + + + + + + + Mouse is at: {{ x }}, {{ y }} + +``` + +## Provide / Inject + +### Basic Usage +```vue + + + + + + + + + +``` + +### With Symbol Keys +```javascript +// keys.js +export const COUNT_KEY = Symbol('count') +export const INCREMENT_KEY = Symbol('increment') + +// ParentComponent.vue +import { provide } from 'vue' +import { COUNT_KEY, INCREMENT_KEY } from './keys' + +const count = ref(0) +provide(COUNT_KEY, count) +provide(INCREMENT_KEY, () => count.value++) + +// ChildComponent.vue +import { inject } from 'vue' +import { COUNT_KEY, INCREMENT_KEY } from './keys' + +const count = inject(COUNT_KEY, 0) +const increment = inject(INCREMENT_KEY) +``` + +## Dynamic Components + +### Basic Dynamic Components +```vue + + + +``` + +### Keep-Alive Components +```vue + + + +``` + +## Async Components + +### Basic Async Component +```javascript +// AsyncComponent.vue +import { defineAsyncComponent } from 'vue' + +export default { + components: { + // Simple usage + AdminPanel: defineAsyncComponent(() => + import('./AdminPanel.vue') + ), + + // With options + UserProfile: defineAsyncComponent({ + // The loader function + loader: () => import('./UserProfile.vue'), + + // A component to use while the async component is loading + loadingComponent: LoadingComponent, + + // A component to use if the load fails + errorComponent: ErrorComponent, + + // Delay before showing the loading component. Default: 200ms + delay: 200, + + // The error component will be displayed if a timeout is + // provided and exceeded. Default: Infinity + timeout: 3000, + + // A function that returns a boolean indicating whether the async component should retry when the loader promise rejects + onError(error, retry, fail, attempts) { + if (error.message.match(/fetch/) && attempts <= 3) { + // Retry on fetch errors, 3 max attempts + retry() + } else { + // Note that retry/fail are like resolve/reject of a promise: + // one of them must be called for the error handling to continue. + fail() + } + } + }) + } +} +``` + +### Suspense (Experimental) +```vue + + + + +``` + +## Composables + +### Creating a Composable +```javascript +// useMouse.js +import { ref, onMounted, onUnmounted } from 'vue' + +export function useMouse() { + const x = ref(0) + const y = ref(0) + + function update(event) { + x.value = event.pageX + y.value = event.pageY + } + + onMounted(() => window.addEventListener('mousemove', update)) + onUnmounted(() => window.removeEventListener('mousemove', update)) + + return { x, y } +} + +// Usage in component +import { useMouse } from './useMouse' + +const { x, y } = useMouse() +``` + +### Async Composable +```javascript +// useFetch.js +import { ref, isRef, unref, watchEffect } from 'vue' + +export function useFetch(url) { + const data = ref(null) + const error = ref(null) + const isPending = ref(false) + + async function doFetch() { + // Reset state + data.value = null + error.value = null + isPending.value = true + + try { + // Unwrap the url in case it's a ref + const urlValue = unref(url) + const response = await fetch(urlValue) + data.value = await response.json() + } catch (e) { + error.value = e + } finally { + isPending.value = false + } + } + + if (isRef(url)) { + // Setup reactive re-fetch if input URL is a ref + watchEffect(doFetch) + } else { + // Otherwise, just fetch once + doFetch() + } + + return { data, error, isPending, retry: doFetch } +} + +// Usage +const { data, isPending, error } = useFetch('https://api.example.com/data') +// Or with a ref +const url = ref('https://api.example.com/data') +const { data } = useFetch(url) +``` + +## Render Functions & JSX + +### Basic Render Function +```javascript +// MyComponent.js +import { h } from 'vue' + +export default { + props: ['level'], + render() { + return h( + 'h' + this.level, // tag name + {}, // props/attributes + this.$slots.default() // array of children + ) + } +} + +// With JSX +import { defineComponent } from 'vue' + +export default defineComponent({ + props: ['level'], + setup(props, { slots }) { + return () => ( +
+ {slots.default?.()} +
+ ) + } +}) +``` + +### Functional Components +```javascript +// FunctionalComponent.js +import { h } from 'vue' + +const FunctionalComponent = (props, { slots, emit, attrs }) => { + return h('div', { class: 'functional' }, [ + h('button', { onClick: () => emit('click') }, 'Click me'), + slots.default?.() + ]) +} + +FunctionalComponent.props = ['title'] +FunctionalComponent.emits = ['click'] + +export default FunctionalComponent +``` + +## Custom Directives + +### Basic Directive +```javascript +// v-focus directive +const vFocus = { + mounted(el) { + el.focus() + } +} + +// Usage in template +// + +// Global registration +app.directive('focus', { + mounted(el) { + el.focus() + } +}) +``` + +### Directive with Arguments and Modifiers +```javascript +const vPin = { + mounted(el, binding) { + el.style.position = 'fixed' + + // Binding value + const s = binding.arg || 'top' + el.style[s] = binding.value + 'px' + + // Modifiers + if (binding.modifiers.animate) { + el.style.transition = 'all 0.3s' + } + }, + + // Updated when the bound value changes + updated(el, binding) { + const s = binding.arg || 'top' + el.style[s] = binding.value + 'px' + } +} + +// Usage +//
Pinned element
+``` + +## Plugins + +### Creating a Plugin +```javascript +// plugins/i18n.js +export default { + install: (app, options) => { + // Add a global method + app.config.globalProperties.$translate = (key) => { + return key.split('.').reduce((o, i) => { + if (o) return o[i] + }, options.messages) + } + + // Provide a value to the app + app.provide('i18n', options) + + // Add a custom directive + app.directive('my-directive', { + mounted(el, binding, vnode, prevVnode) { + // ... + } + }) + + // Add a global mixin + app.mixin({ + created() { + // ... + } + }) + } +} + +// main.js +import { createApp } from 'vue' +import App from './App.vue' +import i18nPlugin from './plugins/i18n' + +const app = createApp(App) + +app.use(i18nPlugin, { + messages: { + welcome: 'Welcome', + buttons: { + save: 'Save', + cancel: 'Cancel' + } + } +}) + +app.mount('#app') +``` + +## Performance Optimization + +### v-once +```vue + +``` + +### v-memo +```vue + +``` + +### Virtual Scrolling +```vue + + + + + +``` + +### Lazy Loading Routes +```javascript +// router.js +import { createRouter, createWebHistory } from 'vue-router' + +const routes = [ + { + path: '/', + component: () => import('./views/Home.vue') + }, + { + path: '/about', + // Route level code-splitting + component: () => import(/* webpackChunkName: "about" */ './views/About.vue') + }, + { + path: '/admin', + // Lazy load admin routes + component: () => import('./views/AdminLayout.vue'), + children: [ + { + path: 'dashboard', + component: () => import('./views/admin/Dashboard.vue') + }, + // ... + ] + } +] + +const router = createRouter({ + history: createWebHistory(), + routes +}) + +export default router +``` + +### Optimizing Updates +```vue + + + +``` + +## Component Design Patterns + +### Compound Components +```vue + + + + + + + + + + + + + + Content for Tab 1 + + + Content for Tab 2 + + +``` + +### Renderless Components +```vue + + + + + + + +
+ Hover over me! +
+
+``` + +### Controlled Components +```vue + + + + + + +``` + +## Testing Components + +### Unit Testing with Vitest +```javascript +// Component.spec.js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +describe('Component', () => { + it('renders correctly', () => { + const wrapper = mount(Component, { + props: { + msg: 'Hello, World!' + }, + global: { + stubs: ['FontAwesomeIcon'] + } + }) + + expect(wrapper.text()).toContain('Hello, World!') + }) + + it('emits an event when clicked', async () => { + const wrapper = mount(Component) + await wrapper.find('button').trigger('click') + expect(wrapper.emitted('click')).toHaveLength(1) + }) +}) +``` + +### Component Testing with Testing Library +```javascript +import { render, screen, fireEvent } from '@testing-library/vue' +import userEvent from '@testing-library/user-event' +import Component from './Component.vue' + +test('renders a greeting', () => { + render(Component, { + props: { + msg: 'Hello, World!' + } + }) + + expect(screen.getByText('Hello, World!')).toBeInTheDocument() +}) + +test('increments counter on button click', async () => { + render(Component) + const button = screen.getByRole('button', { name: /count/i }) + + await userEvent.click(button) + + expect(button).toHaveTextContent('Count: 1') +}) +``` + +## Accessibility (a11y) + +### ARIA Attributes +```vue + + + +``` + +### Focus Management +```vue + + + +``` + +## Internationalization (i18n) + +### Using vue-i18n +```javascript +// plugins/i18n.js +import { createI18n } from 'vue-i18n' + +const messages = { + en: { + message: { + hello: 'Hello!', + welcome: 'Welcome, {name}!' + } + }, + es: { + message: { + hello: '¡Hola!', + welcome: '¡Bienvenido, {name}!' + } + } +} + +const i18n = createI18n({ + locale: 'en', + fallbackLocale: 'en', + messages +}) + +export default i18n + +// main.js +import { createApp } from 'vue' +import App from './App.vue' +import i18n from './plugins/i18n' + +const app = createApp(App) +app.use(i18n) +app.mount('#app') + +// Usage in components + +``` + +## Server-Side Rendering (SSR) + +### Basic SSR with Vite +```javascript +// server.js +import { createSSRApp } from 'vue' +import { renderToString } from 'vue/server-renderer' +import App from './src/App.vue' + +export async function render(url) { + const app = createSSRApp(App) + + const appHtml = await renderToString(app) + + return ` + + + + Vue SSR + + +
${appHtml}
+ + + + ` +} + +// entry-client.js +import { createApp } from 'vue' +import App from './App.vue' + +const app = createApp(App) +app.mount('#app') + +// entry-server.js +import { createSSRApp } from 'vue' +import App from './App.vue' + +export default function () { + const app = createSSRApp(App) + + return { + app + } +} +``` + +## Progressive Web App (PWA) + +### Using Vite PWA Plugin +```bash +npm install -D vite-plugin-pwa +``` + +```javascript +// vite.config.js +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import { VitePWA } from 'vite-plugin-pwa' + +export default defineConfig({ + plugins: [ + vue(), + VitePWA({ + registerType: 'autoUpdate', + includeAssets: ['favicon.ico', 'robots.txt', 'apple-touch-icon.png'], + manifest: { + name: 'My Awesome App', + short_name: 'MyApp', + description: 'My Awesome App description', + theme_color: '#ffffff', + icons: [ + { + src: 'pwa-192x192.png', + sizes: '192x192', + type: 'image/png' + }, + { + src: 'pwa-512x512.png', + sizes: '512x512', + type: 'image/png' + } + ] + } + }) + ] +}) + +// Register service worker in main.js +if ('serviceWorker' in navigator) { + window.addEventListener('load', () => { + navigator.serviceWorker.register('/sw.js') + }) +} +``` + +## Migration from Vue 2 + +### Migration Build +```javascript +// vue.config.js +module.exports = { + configureWebpack: { + resolve: { + alias: { + vue: '@vue/compat', + 'vue-router': 'vue-router/dist/vue-router.compat.js' + } + }, + module: { + rules: [ + { + test: /\.vue$/, + loader: 'vue-loader', + options: { + compilerOptions: { + compatConfig: { + MODE: 2 // or 3 for Vue 3 mode with some Vue 2 compat + } + } + } + } + ] + } + } +} +``` + +### Breaking Changes +1. **Global API**: `Vue` is no longer a constructor, use `createApp` +2. **Template Directives**: `v-model` syntax changed, `v-for` key usage changed +3. **Events API**: `$on`, `$off`, and `$once` removed +4. **Filters** removed, use methods or computed properties +5. **Functional Components** now need to be plain functions +6. **Async Components** now use `defineAsyncComponent` +7. **Render Function** API changed +8. **Custom Directives** lifecycle hooks renamed +9. **Transition** classes changed +10. **v-model** in components now uses `modelValue` and `update:modelValue` + +## Resources + +### Official Documentation +- [Vue 3 Documentation](https://v3.vuejs.org/) +- [Composition API](https://v3.vuejs.org/guide/composition-api-introduction.html) +- [Single File Components](https://v3.vuejs.org/guide/single-file-component.html) +- [Vue Router](https://next.router.vuejs.org/) +- [Pinia](https://pinia.vuejs.org/) +- [VueUse](https://vueuse.org/) + +### Tools +- [Vue DevTools](https://devtools.vuejs.org/) +- [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar) +- [Vitest](https://vitest.dev/) +- [Vue Test Utils](https://next.vue-test-utils.vuejs.org/) +- [VueUse](https://vueuse.org/) + +### UI Frameworks +- [Vuetify](https://next.vuetifyjs.com/) +- [Quasar](https://quasar.dev/) +- [Element Plus](https://element-plus.org/) +- [Naive UI](https://www.naiveui.com/) +- [PrimeVue](https://primefaces.org/primevue/) + +### Learning Resources +- [Vue Mastery](https://www.vuemastery.com/) +- [Vue School](https://vueschool.io/) +- [Vue.js Developers](https://vuejsdevelopers.com/) +- [VueDose](https://vuedose.tips/) diff --git a/docs/vue/reactivity.md b/docs/vue/reactivity.md new file mode 100644 index 0000000..3c63034 --- /dev/null +++ b/docs/vue/reactivity.md @@ -0,0 +1,533 @@ +# Vue Reactivity System + +## Table of Contents +1. [Introduction to Reactivity](#introduction-to-reactivity) +2. [Reactive State](#reactive-state) +3. [Reactive Objects](#reactive-objects) +4. [Reactive Arrays](#reactive-arrays) +5. [Reactive Refs](#reactive-refs) +6. [Computed Properties](#computed-properties) +7. [Watching Reactive Data](#watching-reactive-data) +8. [Effect Scope](#effect-scope) +9. [Reactivity Utilities](#reactivity-utilities) +10. [Performance Considerations](#performance-considerations) + +## Introduction to Reactivity + +Vue's reactivity system is the foundation of its component model. When you change data, the view updates automatically. + +### How Reactivity Works +1. **Proxy-based** (Vue 3): Uses JavaScript Proxies to track property access +2. **Getter/Setter** (Vue 2): Uses Object.defineProperty for reactivity + +## Reactive State + +### `reactive()` +Creates a reactive object (works with objects, arrays, Map, Set). + +```typescript +import { reactive } from 'vue' + +const state = reactive({ + count: 0, + user: { + name: 'John', + age: 30 + }, + todos: [] +}) + +// Nested objects are reactive +state.user.name = 'Jane' // triggers reactivity +``` + +### `ref()` +Creates a reactive reference to a value. + +```typescript +import { ref } from 'vue' + +const count = ref(0) // { value: 0 } +const user = ref({ + name: 'John', + age: 30 +}) + +// Access with .value +count.value++ // triggers reactivity +user.value.name = 'Jane' // also reactive + +// In templates, no .value needed +//
{{ count }}
+``` + +### `readonly()` +Creates a read-only proxy of the original object. + +```typescript +import { reactive, readonly } from 'vue' + +const original = reactive({ count: 0 }) +const copy = readonly(original) + +// Mutating original will trigger updates in copy +original.count++ // works +copy.count++ // warning: cannot mutate readonly property +``` + +## Reactive Objects + +### Shallow Reactive +Only the top-level properties are reactive. + +```typescript +import { shallowReactive } from 'vue' + +const state = shallowReactive({ + count: 0, + user: { name: 'John' } // nested object is NOT reactive +}) + +state.count++ // reactive +state.user.name = 'Jane' // NOT reactive +``` + +### Reactive vs Ref + +| Feature | `reactive` | `ref` | +|-------------------|------------|-------| +| Works with | Objects/Arrays | Any value | +| Access in JS | Direct | `.value` | +| Template usage | Direct | Unwraps automatically | +| TypeScript support | Good | Excellent | +| Watch deep by default | Yes | No | + +## Reactive Arrays + +### Array Reactivity +```typescript +const list = reactive([1, 2, 3]) + +// These methods trigger updates +list.push(4) +list.pop() +list.splice(0, 1, 5) +list.sort() +list.reverse() + +// Replacing the entire array works +list = [...list, 4] // with let +list.splice(0, list.length, ...newList) // with const +``` + +### Caveats with Arrays +```typescript +// These won't trigger updates +list[0] = 5 // Won't work +list.length = 0 // Won't work + +// Solutions +list.splice(0, 1, 5) // Use array methods +list.splice(0) // Clear array +``` + +## Computed Properties + +### Basic Usage +```typescript +import { ref, computed } from 'vue' + +const count = ref(0) +const double = computed(() => count.value * 2) + +console.log(double.value) // 0 +count.value++ +console.log(double.value) // 2 +``` + +### Writable Computed +```typescript +const firstName = ref('John') +const lastName = ref('Doe') + +const fullName = computed({ + get() { + return `${firstName.value} ${lastName.value}` + }, + set(newValue) { + [firstName.value, lastName.value] = newValue.split(' ') + } +}) + +fullName.value = 'Jane Smith' // Updates firstName and lastName +``` + +## Watching Reactive Data + +### `watch` +```typescript +import { ref, watch } from 'vue' + +const count = ref(0) +const user = reactive({ name: 'John' }) + +// Watch a single ref +watch(count, (newValue, oldValue) => { + console.log(`Count changed from ${oldValue} to ${newValue}`) +}) + +// Watch a getter function +watch( + () => user.name, + (newName, oldName) => { + console.log(`Name changed from ${oldName} to ${newName}`) + } +) + +// Watch multiple sources +watch( + [() => user.name, count], + ([newName, newCount], [oldName, oldCount]) => { + console.log('Name or count changed') + } +) +``` + +### `watchEffect` +Runs immediately and tracks reactive dependencies automatically. + +```typescript +import { ref, watchEffect } from 'vue' + +const count = ref(0) +const double = ref(0) + +watchEffect(() => { + // Automatically tracks count + double.value = count.value * 2 +}) + +count.value++ // Triggers effect +``` + +### Watch Options +```typescript +watch( + source, + callback, + { + immediate: true, // Run immediately + deep: true, // Deep watch + flush: 'post', // Run after DOM updates + onTrack(e) { // Debugging + debugger + }, + onTrigger(e) { // Debugging + debugger + } + } +) +``` + +## Effect Scope + +### `effectScope` +Groups multiple effects together for better organization and cleanup. + +```typescript +import { effectScope, ref, watch, watchEffect } from 'vue' + +const scope = effectScope() + +scope.run(() => { + const count = ref(0) + + watch(count, () => console.log('Count changed')) + watchEffect(() => console.log('Effect:', count.value)) + + // All effects are stopped when scope is stopped + setTimeout(() => scope.stop(), 1000) +}) +``` + +## Reactivity Utilities + +### `toRef` and `toRefs` +```typescript +import { reactive, toRef, toRefs } from 'vue' + +const state = reactive({ + foo: 1, + bar: 2 +}) + +// Convert a single property to a ref +const fooRef = toRef(state, 'foo') + +// Convert all properties to refs +const { foo, bar } = toRefs(state) + +// Now you can pass refs around without losing reactivity +function useFeatureX(foo, bar) { + // Both are refs that stay synced with the original + return { + foo, + bar, + sum: computed(() => foo.value + bar.value) + } +} +``` + +### `isReactive` and `isRef` +```typescript +import { isReactive, isRef, reactive, ref } from 'vue' + +console.log(isReactive(reactive({}))) // true +console.log(isRef(ref(0))) // true +``` + +### `shallowRef` and `triggerRef` +```typescript +import { shallowRef, triggerRef } from 'vue' + +const state = shallowRef({ count: 0 }) + +// Doesn't trigger updates for nested properties +state.value.count++ // No update + +// Force update +triggerRef(state) // Triggers update +``` + +## Performance Considerations + +1. **Avoid Large Reactive Objects** + - Keep reactive state minimal + - Use `shallowRef` or `shallowReactive` for large objects + +2. **Debounce Expensive Operations** + ```typescript + import { debounce } from 'lodash-es' + + const searchQuery = ref('') + const searchResults = ref([]) + + const search = debounce(async () => { + searchResults.value = await fetchResults(searchQuery.value) + }, 500) + + watch(searchQuery, search) + ``` + +3. **Use `computed` for Derived State** + - Caches results until dependencies change + - More efficient than methods in templates + +4. **Batch Updates** + ```typescript + import { nextTick } from 'vue' + + // Multiple state updates in the same tick + const update = () => { + state.a = 1 + state.b = 2 + // DOM updates once after this function + } + + // Or use nextTick + const update = async () => { + state.a = 1 + await nextTick() + // DOM is updated + state.b = 2 + } + ``` + +5. **Virtual Scrolling for Large Lists** + ```vue + + + + + + ``` + +## Common Pitfalls + +1. **Destructuring Reactive Objects** + ```typescript + // ❌ Loses reactivity + const { x, y } = reactive({ x: 0, y: 0 }) + + // ✅ Use toRefs + const pos = reactive({ x: 0, y: 0 }) + const { x, y } = toRefs(pos) + ``` + +2. **Async Updates** + ```typescript + // ❌ May miss updates + const update = async () => { + state.data = await fetchData() + // DOM not updated yet + doSomethingWithDOM() + } + + // ✅ Use nextTick + const update = async () => { + state.data = await fetchData() + await nextTick() + // DOM is updated + doSomethingWithDOM() + } + ``` + +3. **Circular References** + ```typescript + // ❌ Creates infinite loop + const obj = reactive({}) + obj.self = obj // Circular reference + + // ✅ Break the cycle + const obj = reactive({ + self: null as any + }) + obj.self = obj // Still reactive, but no infinite loop + ``` + +## Advanced Reactivity Patterns + +### Custom Ref +```typescript +import { customRef } from 'vue' + +function useDebouncedRef(value, delay = 200) { + let timeout + return customRef((track, trigger) => { + return { + get() { + track() + return value + }, + set(newValue) { + clearTimeout(timeout) + timeout = setTimeout(() => { + value = newValue + trigger() + }, delay) + } + } + }) +} + +// Usage +const text = useDebouncedRef('hello') +``` + +### Reactive State Machine +```typescript +import { reactive } from 'vue' + +function createMachine(config) { + const state = reactive({ + current: config.initial, + transition(event) { + const currentState = config.states[state.current] + const nextState = currentState?.on?.[event] + + if (nextState) { + currentState.onExit?.() + state.current = nextState + config.states[nextState].onEnter?.() + return true + } + return false + } + }) + + return state +} + +// Usage +const trafficLight = createMachine({ + initial: 'red', + states: { + red: { + on: { next: 'green' }, + onEnter: () => console.log('Stop!') + }, + yellow: { + on: { next: 'red' }, + onEnter: () => console.log('Slow down!') + }, + green: { + on: { next: 'yellow' }, + onEnter: () => console.log('Go!') + } + } +}) + +trafficLight.transition('next') // Logs: "Go!" +``` diff --git a/docs/vue/routing.md b/docs/vue/routing.md new file mode 100644 index 0000000..f585ada --- /dev/null +++ b/docs/vue/routing.md @@ -0,0 +1,1084 @@ +# Vue Router: Official Router for Vue.js + +## Table of Contents +- [Introduction to Vue Router](#introduction-to-vue-router) +- [Installation & Setup](#installation--setup) +- [Basic Routing](#basic-routing) +- [Dynamic Route Matching](#dynamic-route-matching) +- [Nested Routes](#nested-routes) +- [Programmatic Navigation](#programmatic-navigation) +- [Named Routes & Views](#named-routes--views) +- [Route Parameters & Props](#route-parameters--props) +- [Navigation Guards](#navigation-guards) +- [Route Meta Fields](#route-meta-fields) +- [Transitions](#transitions) +- [Data Fetching](#data-fetching) +- [Scroll Behavior](#scroll-behavior) +- [Lazy Loading Routes](#lazy-loading-routes) +- [Navigation Failures](#navigation-failures) +- [Composition API](#composition-api) +- [TypeScript Support](#typescript-support) +- [Authentication & Protected Routes](#authentication--protected-routes) +- [Error Handling](#error-handling) +- [Performance Optimization](#performance-optimization) +- [Deployment](#deployment) +- [Common Patterns](#common-patterns) +- [Migration from Vue 2](#migration-from-vue-2) + +## Introduction to Vue Router + +Vue Router is the official router for Vue.js. It deeply integrates with Vue.js core to make building Single Page Applications with Vue.js a breeze. + +### Key Features +- Nested route/view mapping +- Modular, component-based router configuration +- Route params, query, wildcards +- View transition effects +- Fine-grained navigation control +- Links with automatic active CSS classes +- HTML5 history or hash mode, with auto-fallback in IE9 +- Customizable scroll behavior +- Proper encoding for URLs +- Route level code-splitting + +## Installation & Setup + +### Installation + +```bash +# For Vue 3 +npm install vue-router@4 +# or with yarn +yarn add vue-router@4 +``` + +### Basic Setup + +```javascript +// router/index.js +import { createRouter, createWebHistory } from 'vue-router' +import Home from '../views/Home.vue' + +const routes = [ + { + path: '/', + name: 'Home', + component: Home + }, + { + path: '/about', + name: 'About', + component: () => import('../views/About.vue') + } +] + +const router = createRouter({ + history: createWebHistory(process.env.BASE_URL), + routes +}) + +export default router +``` + +```javascript +// main.js +import { createApp } from 'vue' +import App from './App.vue' +import router from './router' + +const app = createApp(App) +app.use(router) +app.mount('#app') +``` + +### Router View + +```vue + + +``` + +## Basic Routing + +### Route Configuration + +```javascript +const routes = [ + { path: '/', component: Home }, + { path: '/about', component: About }, + { path: '/contact', component: Contact } +] +``` + +### HTML5 History Mode + +```javascript +import { createRouter, createWebHistory } from 'vue-router' + +const router = createRouter({ + history: createWebHistory(), + routes +}) +``` + +### Hash Mode + +```javascript +import { createRouter, createWebHashHistory } from 'vue-router' + +const router = createRouter({ + history: createWebHashHistory(), + routes +}) +``` + +## Dynamic Route Matching + +### Dynamic Segments + +```javascript +const routes = [ + // Dynamic segment starts with a colon + { path: '/users/:id', component: User }, + + // Multiple dynamic segments + { path: '/users/:username/posts/:postId', component: UserPost } +] +``` + +### Accessing Route Parameters + +```vue + + + +``` + +### Catch All / 404 Not Found Route + +```javascript +{ + // Will match everything and put it under `$route.params.pathMatch` + path: '/:pathMatch(.*)*', + name: 'NotFound', + component: NotFound +} +``` + +## Nested Routes + +```javascript +const routes = [ + { + path: '/user/:id', + component: User, + children: [ + { + // UserProfile will be rendered inside User's + // when /user/:id/profile is matched + path: 'profile', + component: UserProfile + }, + { + // UserPosts will be rendered inside User's + // when /user/:id/posts is matched + path: 'posts', + component: UserPosts + }, + { + // Redirect /user/:id to /user/:id/profile + path: '', + redirect: 'profile' + } + ] + } +] +``` + +## Programmatic Navigation + +### Navigation Methods + +```javascript +// Navigate to a different URL +router.push('/users/1') + +// Navigate with object +router.push({ path: '/users/1' }) + +// Named route +router.push({ name: 'user', params: { id: '1' } }) + +// With query parameters +router.push({ path: '/users', query: { page: '1' } }) + +// Replace current entry (no browser history) +router.replace({ path: '/home' }) + +// Go forward/back +router.go(1) // forward 1 page +router.go(-1) // back 1 page +``` + +### In Component Methods + +```javascript +// Options API +methods: { + goToUser() { + this.$router.push('/users/1') + } +} + +// Composition API +import { useRouter } from 'vue-router' + +export default { + setup() { + const router = useRouter() + + const goToUser = () => { + router.push('/users/1') + } + + return { goToUser } + } +} +``` + +## Named Routes & Views + +### Named Routes + +```javascript +const routes = [ + { + path: '/user/:id', + name: 'user', + component: User + } +] + +// Navigation +router.push({ name: 'user', params: { id: 1 } }) +``` + +### Named Views + +```vue + + + +``` + +## Route Parameters & Props + +### Boolean Mode + +```javascript +const routes = [ + { + path: '/user/:id', + component: User, + props: true // Pass route.params as props + } +] +``` + +### Object Mode + +```javascript +const routes = [ + { + path: '/user', + component: User, + props: { default: true, id: '123' } + } +] +``` + +### Function Mode + +```javascript +const routes = [ + { + path: '/search', + component: Search, + props: route => ({ query: route.query.q }) + } +] +``` + +## Navigation Guards + +### Global Before Guards + +```javascript +const router = createRouter({ ... }) + +router.beforeEach((to, from, next) => { + // Must call `next` + if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' }) + else next() +}) +``` + +### Per-Route Guards + +```javascript +const routes = [ + { + path: '/admin', + component: Admin, + beforeEnter: (to, from, next) => { + if (isAdmin) next() + else next('/login') + } + } +] +``` + +### In-Component Guards + +```javascript +const UserDetails = { + template: '...', + beforeRouteEnter(to, from, next) { + // Called before the route that renders this component is confirmed. + // Does NOT have access to `this` component instance. + next(vm => { + // Access component instance via `vm` + }) + }, + beforeRouteUpdate(to, from) { + // Called when the route that renders this component has changed, + // but this component is reused in the new route. + // Has access to `this` component instance. + this.userData = fetchUser(to.params.id) + }, + beforeRouteLeave(to, from) { + // Called when the route that renders this component is about to + // be navigated away from. + const answer = window.confirm('Do you really want to leave?') + if (!answer) return false + } +} +``` + +## Route Meta Fields + +```javascript +const routes = [ + { + path: '/profile', + component: Profile, + meta: { requiresAuth: true } + }, + { + path: '/admin', + component: Admin, + meta: { requiresAdmin: true } + } +] + +// Navigation guard example +router.beforeEach((to, from, next) => { + if (to.meta.requiresAuth && !isAuthenticated) { + next('/login') + } else if (to.meta.requiresAdmin && !isAdmin) { + next('/unauthorized') + } else { + next() + } +}) +``` + +## Transitions + +### Per-Route Transition + +```vue + + + +``` + +### Different Transitions per Route + +```vue + +``` + +## Data Fetching + +### Fetching Before Navigation + +```javascript +const routes = [ + { + path: '/user/:id', + component: User, + props: true, + beforeEnter: async (to, from, next) => { + try { + const user = await fetchUser(to.params.id) + to.meta.user = user + next() + } catch (error) { + next('/error') + } + } + } +] +``` + +### Fetching After Navigation + +```vue + + + +``` + +## Scroll Behavior + +```javascript +const router = createRouter({ + history: createWebHistory(), + routes, + scrollBehavior(to, from, savedPosition) { + // Scroll to top for all route navigations + if (savedPosition) { + return savedPosition + } else if (to.hash) { + return { + el: to.hash, + behavior: 'smooth', + } + } else { + return { top: 0, behavior: 'smooth' } + } + } +}) +``` + +## Lazy Loading Routes + +### Dynamic Imports + +```javascript +const routes = [ + { + path: '/about', + name: 'About', + component: () => import('../views/About.vue') + }, + { + path: '/contact', + name: 'Contact', + component: () => import(/* webpackChunkName: "contact" */ '../views/Contact.vue') + } +] +``` + +### Grouping Components in the Same Chunk + +```javascript +// webpackChunkName comments put these in the same chunk +const UserDetails = () => import(/* webpackChunkName: "users" */ './UserDetails.vue') +const UserPosts = () => import(/* webpackChunkName: "users" */ './UserPosts.vue') +``` + +## Navigation Failures + +### Detecting Navigation Failures + +```javascript +// After navigation +const navigationResult = await router.push('/some-path') + +if (navigationResult) { + // Navigation was prevented + console.log('Navigation was prevented') + + if (isNavigationFailure(navigationResult, NavigationFailureType.aborted)) { + console.log('Navigation was aborted') + } else if (isNavigationFailure(navigationResult, NavigationFailureType.cancelled)) { + console.log('Navigation was cancelled') + } +} else { + // Navigation was successful + console.log('Navigation succeeded') +} +``` + +## Composition API + +### useRouter & useRoute + +```vue + + + +``` + +## TypeScript Support + +### Typed Routes + +```typescript +// router/index.ts +import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' + +const routes: Array = [ + { + path: '/', + name: 'Home', + component: () => import('../views/Home.vue') + }, + { + path: '/about', + name: 'About', + component: () => import('../views/About.vue') + } +] + +const router = createRouter({ + history: createWebHistory(process.env.BASE_URL), + routes +}) + +export default router +``` + +### Typed Navigation + +```typescript +import { useRouter } from 'vue-router' + +declare module 'vue-router' { + interface RouteMeta { + requiresAuth?: boolean + requiresAdmin?: boolean + } +} + +const router = useRouter() + +// Typed navigation +router.push({ + name: 'UserProfile', + params: { id: 1 } // Type-checked +}) +``` + +## Authentication & Protected Routes + +### Route Guard for Authentication + +```typescript +// router/guards/authGuard.ts +import { NavigationGuardNext, RouteLocationNormalized } from 'vue-router' +import { useAuthStore } from '@/stores/auth' + +export default function authGuard( + to: RouteLocationNormalized, + from: RouteLocationNormalized, + next: NavigationGuardNext +) { + const authStore = useAuthStore() + + if (to.meta.requiresAuth && !authStore.isAuthenticated) { + next({ + name: 'Login', + query: { redirect: to.fullPath } + }) + } else if (to.meta.requiresAdmin && !authStore.isAdmin) { + next({ name: 'Unauthorized' }) + } else { + next() + } +} +``` + +### Login Flow + +```vue + + + +``` + +## Error Handling + +### 404 Not Found + +```javascript +// router/index.js +const routes = [ + // ... other routes ... + { + path: '/:pathMatch(.*)*', + name: 'NotFound', + component: () => import('@/views/NotFound.vue') + } +] +``` + +### Global Error Handler + +```javascript +// main.js +import { createApp } from 'vue' +import App from './App.vue' +import router from './router' + +const app = createApp(App) + +// Global error handler for navigation errors +app.config.errorHandler = (err, vm, info) => { + console.error('Vue error:', err) + console.error('Error in component:', vm) + console.error('Error info:', info) + + // You could redirect to an error page here + // router.push('/error') +} + +// Global error handler for unhandled promise rejections +window.addEventListener('unhandledrejection', (event) => { + console.error('Unhandled promise rejection:', event.reason) + // Prevent the default browser error handling + event.preventDefault() +}) + +app.use(router) +app.mount('#app') +``` + +## Performance Optimization + +### Route-Level Code Splitting + +```javascript +const routes = [ + { + path: '/dashboard', + component: () => import(/* webpackChunkName: "dashboard" */ '@/views/Dashboard.vue') + }, + { + path: '/settings', + component: () => import(/* webpackChunkName: "settings" */ '@/views/Settings.vue') + } +] +``` + +### Prefetching Routes + +```vue + + + +``` + +### Lazy Loading with Suspense + +```vue + +``` + +## Deployment + +### History Mode Server Configuration + +#### Nginx + +```nginx +location / { + try_files $uri $uri/ /index.html; +} +``` + +#### Apache + +```apache + + RewriteEngine On + RewriteBase / + RewriteRule ^index\.html$ - [L] + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule . /index.html [L] + +``` + +### Base URL + +```javascript +const router = createRouter({ + history: createWebHistory('/my-app/'), + routes +}) +``` + +## Common Patterns + +### Route-Based Code Splitting + +```javascript +// router/modules/admin.js +const AdminDashboard = () => import('@/views/admin/Dashboard.vue') +const AdminUsers = () => import('@/views/admin/Users.vue') + +export default [ + { + path: '/admin', + component: () => import('@/layouts/AdminLayout.vue'), + children: [ + { path: '', component: AdminDashboard }, + { path: 'users', component: AdminUsers } + ] + } +] +``` + +### Scroll to Top on Route Change + +```javascript +const router = createRouter({ + // ... + scrollBehavior(to, from, savedPosition) { + if (savedPosition) { + return savedPosition + } else { + return { top: 0, behavior: 'smooth' } + } + } +}) +``` + +### Query Parameter Changes + +```vue + + + +``` + +## Migration from Vue 2 + +### Key Changes in Vue Router 4 + +1. **New `createRouter` function** + ```javascript + // Vue Router 3 + const router = new VueRouter({ ... }) + + // Vue Router 4 + import { createRouter, createWebHistory } from 'vue-router' + const router = createRouter({ + history: createWebHistory(), + routes: [] + }) + ``` + +2. **New Navigation API** + ```javascript + // Vue Router 3 + this.$router.push('/path') + + // Vue Router 4 (Composition API) + import { useRouter } from 'vue-router' + const router = useRouter() + router.push('/path') + ``` + +3. **Route Properties** + - `$route` is now accessed via `useRoute()` in Composition API + - `parent` and `children` properties are removed from route records + +4. **Navigation Guards** + - `next` parameter is now optional in navigation guards + - `next(false)` is now `return false` + - `next('/')` is now `return '/'` + +5. **Scroll Behavior** + - The `x` and `y` properties are now `left` and `top` + - The `selector` option is replaced with `el` + +### Migration Strategy + +1. Update Vue Router to version 4 + ```bash + npm install vue-router@4 + # or + yarn add vue-router@4 + ``` + +2. Update your router configuration + - Replace `new VueRouter()` with `createRouter()` + - Replace `mode: 'history'` with `history: createWebHistory()` + - Update any scroll behavior functions + +3. Update your components + - Replace `this.$route` with `useRoute()` in setup() + - Replace `this.$router` with `useRouter()` in setup() + - Update any navigation guards + +4. Test thoroughly + - Test all navigation flows + - Check for any direct route property access + - Verify scroll behavior + +5. Update tests + - Update any tests that interact with the router + - Use the new testing utilities if needed + +## Conclusion + +Vue Router is a powerful and flexible routing library for Vue.js applications. By following the patterns and best practices outlined in this guide, you can build complex, maintainable, and performant single-page applications. Whether you're building a small project or a large-scale application, Vue Router provides the tools you need to manage navigation and state effectively. diff --git a/docs/vue/setup.md b/docs/vue/setup.md new file mode 100644 index 0000000..21d6bae --- /dev/null +++ b/docs/vue/setup.md @@ -0,0 +1,323 @@ +# Vue.js Setup Guide + +## Table of Contents +1. [Installation](#installation) +2. [Project Setup](#project-setup) +3. [Vue CLI](#vue-cli) +4. [Vite](#vite) +5. [CDN](#cdn) +6. [Development Server](#development-server) +7. [Build for Production](#build-for-production) +8. [Configuration](#configuration) +9. [IDE Setup](#ide-setup) +10. [Browser DevTools](#browser-devtools) + +## Installation + +### Prerequisites +- Node.js 16.0.0 or later +- npm or yarn + +### Vue CLI (Legacy) +```bash +# Install Vue CLI globally +npm install -g @vue/cli +# OR +yarn global add @vue/cli + +# Check installation +vue --version +``` + +### Create a New Project +#### Using Vue CLI +```bash +vue create my-vue-app +cd my-vue-app +``` + +#### Using Vite (Recommended for new projects) +```bash +# npm 7+, extra double-dash is needed: +npm create vue@latest my-vue-app -- --typescript --router --pinia --vitest --eslint + +# npm 6.x +npm create vue@latest my-vue-app --template typescript --router --pinia --vitest --eslint + +# yarn +yarn create vue my-vue-app --template typescript --router --pinia --vitest --eslint + +# pnpm +pnpm create vue my-vue-app -- --typescript --router --pinia --vitest --eslint + +# Navigate to project +cd my-vue-app + +# Install dependencies +npm install +# OR +yarn +# OR +pnpm install +``` + +## Project Structure + +### Vue 3 Project Structure +``` +my-vue-app/ +├── public/ # Static files +│ ├── favicon.ico +│ └── index.html # Main HTML file +├── src/ +│ ├── assets/ # Static assets (images, styles) +│ ├── components/ # Reusable components +│ ├── composables/ # Composable functions +│ ├── router/ # Vue Router configuration +│ ├── stores/ # State management (Pinia) +│ ├── views/ # Route components +│ ├── App.vue # Root component +│ └── main.js # Application entry point +├── .eslintrc.js # ESLint config +├── .gitignore +├── index.html +├── package.json +├── README.md +├── vite.config.js # Vite config +└── tsconfig.json # TypeScript config +``` + +### Main Application File +```typescript +// src/main.ts +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import App from './App.vue' +import router from './router' + +// Import global styles +import './assets/main.css' + +const app = createApp(App) + +// Use plugins +app.use(createPinia()) +app.use(router) + +// Mount the app +app.mount('#app') +``` + +## CDN + +### Development Version +```html + + +``` + +### Production Version +```html + + +``` + +### ES Modules Build +```html + +``` + +## Development Server + +### Vite Dev Server +```bash +# Start development server +npm run dev +# OR +yarn dev +# OR +pnpm dev + +# Access at http://localhost:5173 +``` + +### Vue CLI Dev Server (legacy) +```bash +npm run serve +# OR +yarn serve +``` + +## Build for Production + +### Vite Build +```bash +# Build for production +npm run build +# OR +yarn build +# OR +pnpm build + +# Preview production build locally +npm run preview +``` + +### Vue CLI Build (legacy) +```bash +npm run build +# OR +yarn build +``` + +## Configuration + +### Vite Configuration (vite.config.js) +```javascript +import { fileURLToPath, URL } from 'node:url' +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + } + }, + server: { + port: 3000, + open: true + }, + build: { + outDir: 'dist', + assetsDir: 'assets', + sourcemap: true + } +}) +``` + +### Environment Variables +Create `.env` files in your project root: + +```env +# .env +VITE_APP_TITLE=My Vue App +VITE_API_URL=https://api.example.com +``` + +Access in your code: +```typescript +const apiUrl = import.meta.env.VITE_API_URL +``` + +## IDE Setup + +### VS Code Extensions +1. [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) - Official Vue Language Features +2. [TypeScript Vue Plugin](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) - TypeScript support for Vue +3. [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) - JavaScript/TypeScript linting +4. [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) - Code formatter +5. [Stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint) - CSS/SCSS linting + +### Recommended VS Code Settings +```json +{ + "editor.tabSize": 2, + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true, + "source.organizeImports": true + }, + "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact", "vue"], + "[vue]": { + "editor.defaultFormatter": "Vue.volar" + }, + "typescript.tsdk": "node_modules/typescript/lib" +} +``` + +## Browser DevTools + +### Vue Devtools +1. Install [Vue Devtools](https://devtools.vuejs.org/guide/installation.html) browser extension +2. For local development, it should automatically detect your Vue application +3. For production, add this to your main application file: + +```typescript +// Only in development +if (import.meta.env.DEV) { + const { createApp } = await import('vue') + const { default: App } = await import('./App.vue') + + // Mount devtools + if (window.__VUE_DEVTOOLS_GLOBAL_HOOK__) { + window.__VUE_DEVTOOLS_GLOBAL_HOOK__.Vue = createApp + } +} +``` + +### Debugging +- Use `debugger` statements in your code +- Utilize Vue Devtools component inspection +- Check the browser's console for warnings and errors + +## Common Issues + +### Resolving Dependency Issues +```bash +# Delete node_modules and package-lock.json +rm -rf node_modules package-lock.json + +# Clear npm cache +npm cache clean --force + +# Reinstall dependencies +npm install +``` + +### TypeScript Support +Ensure your `tsconfig.json` includes: +```json +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "preserve", + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true, + "noEmit": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} +``` + +### Vue 2 vs Vue 3 +- Vue 3 is the current major version with Composition API +- Vue 2 is in maintenance mode (end of life: Dec 31, 2023) +- Use `@vue/compat` for migration from Vue 2 to 3 + +## Next Steps +- [Learn about Components](./components.md) +- [Understand Reactivity System](./reactivity.md) +- [State Management with Pinia/Vuex](./vuex.md) +- [Routing with Vue Router](./routing.md) diff --git a/docs/vue/vuex.md b/docs/vue/vuex.md new file mode 100644 index 0000000..b358be6 --- /dev/null +++ b/docs/vue/vuex.md @@ -0,0 +1,502 @@ +# Vuex: State Management for Vue.js + +## Table of Contents +- [Introduction to Vuex](#introduction-to-vuex) +- [Core Concepts](#core-concepts) + - [State](#state) + - [Getters](#getters) + - [Mutations](#mutations) + - [Actions](#actions) + - [Modules](#modules) +- [Setting Up Vuex](#setting-up-vuex) +- [Basic Usage](#basic-usage) +- [Advanced Patterns](#advanced-patterns) +- [Composition API with Vuex](#composition-api-with-vuex) +- [Testing Vuex](#testing-vuex) +- [Vuex vs. Pinia](#vuex-vs-pinia) +- [Best Practices](#best-practices) +- [Migration from Vuex 3 to 4](#migration-from-vuex-3-to-4) +- [Common Pitfalls](#common-pitfalls) +- [Performance Optimization](#performance-optimization) +- [Useful Plugins](#useful-plugins) + +## Introduction to Vuex + +Vuex is a state management pattern + library for Vue.js applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion. + +### When to Use Vuex +- Large-scale applications with complex state management needs +- Multiple components need to share state +- State needs to be accessed by many components at different nesting levels +- State needs to be preserved across page reloads +- State needs to be tracked and debugged + +## Core Concepts + +### State + +```javascript +const store = createStore({ + state: { + count: 0, + todos: [] + } +}) +``` + +### Getters + +```javascript +const store = createStore({ + state: { + todos: [ + { id: 1, text: 'Learn Vue', done: true }, + { id: 2, text: 'Learn Vuex', done: false } + ] + }, + getters: { + doneTodos: (state) => { + return state.todos.filter(todo => todo.done) + }, + getTodoById: (state) => (id) => { + return state.todos.find(todo => todo.id === id) + } + } +}) +``` + +### Mutations + +```javascript +const store = createStore({ + state: { + count: 1 + }, + mutations: { + increment (state) { + state.count++ + }, + incrementBy (state, payload) { + state.count += payload.amount + } + } +}) + +// Commit a mutation +store.commit('increment') +store.commit('incrementBy', { amount: 10 }) +``` + +### Actions + +```javascript +const store = createStore({ + state: { + count: 0 + }, + mutations: { + increment (state) { + state.count++ + } + }, + actions: { + incrementAsync ({ commit }) { + setTimeout(() => { + commit('increment') + }, 1000) + }, + async fetchData({ commit }) { + try { + const response = await fetch('api/data') + const data = await response.json() + commit('setData', data) + } catch (error) { + console.error('Error fetching data:', error) + } + } + } +}) + +// Dispatch an action +store.dispatch('incrementAsync') +``` + +### Modules + +```javascript +const moduleA = { + namespaced: true, + state: { count: 0 }, + mutations: { + increment (state) { + state.count++ + } + }, + getters: { + doubleCount (state) { + return state.count * 2 + } + } +} + +const store = createStore({ + modules: { + a: moduleA + } +}) + +// Access module state +store.state.a.count + +// Access module getter +store.getters['a/doubleCount'] + +// Commit module mutation +store.commit('a/increment') + +// Dispatch module action +store.dispatch('a/incrementAsync') +``` + +## Setting Up Vuex + +### Installation + +```bash +# For Vue 3 +npm install vuex@next --save +# or with yarn +yarn add vuex@next +``` + +### Basic Store + +```javascript +import { createApp } from 'vue' +import { createStore } from 'vuex' + +const store = createStore({ + state: { + count: 0 + }, + mutations: { + increment (state) { + state.count++ + } + }, + actions: { + incrementAsync ({ commit }) { + setTimeout(() => { + commit('increment') + }, 1000) + } + }, + getters: { + getCount: state => state.count + } +}) + +const app = createApp({ /* your root component */ }) +app.use(store) +app.mount('#app') +``` + +## Composition API with Vuex + +```vue + + + +``` + +## Testing Vuex + +### Testing Mutations + +```javascript +import { createStore } from 'vuex' +import { mutations } from '@/store/mutations' + +describe('mutations', () => { + let store + + beforeEach(() => { + store = createStore({ + state: { count: 0 }, + mutations + }) + }) + + test('increment increments state.count by 1', () => { + store.commit('increment') + expect(store.state.count).toBe(1) + }) +}) +``` + +### Testing Actions + +```javascript +import { createStore } from 'vuex' +import { actions } from '@/store/actions' + +// Mock API or service +jest.mock('@/api', () => ({ + fetchData: jest.fn(() => Promise.resolve({ data: 'mocked data' })) +})) + +describe('actions', () => { + let store + + beforeEach(() => { + store = createStore({ + state: { data: null }, + mutations: { + setData: (state, data) => { state.data = data } + }, + actions + }) + }) + + test('fetchData commits the response', async () => { + await store.dispatch('fetchData') + expect(store.state.data).toBe('mocked data') + }) +}) +``` + +## Vuex vs. Pinia + +| Feature | Vuex | Pinia | +|---------|------|-------| +| Vue 3 Support | Vuex 4+ | Built for Vue 3 | +| TypeScript | Partial | First-class | +| DevTools | Yes | Yes | +| Composition API | Yes | Built with it | +| Size | ~1.5KB | ~1KB | +| Modules | Required | Optional | +| Code Splitting | Manual | Automatic | + +## Best Practices + +1. **Strict Mode** + ```javascript + const store = createStore({ + // ... + strict: process.env.NODE_ENV !== 'production' + }) + ``` + +2. **Use Constants for Mutation Types** + ```javascript + // mutation-types.js + export const SOME_MUTATION = 'SOME_MUTATION' + + // store.js + import { SOME_MUTATION } from './mutation-types' + + const store = createStore({ + state: { ... }, + mutations: { + [SOME_MUTATION] (state) { + // mutate state + } + } + }) + ``` + +3. **Organize with Modules** + ``` + store/ + ├── index.js # where we assemble modules and export the store + ├── actions.js # root actions + ├── mutations.js # root mutations + └── modules/ + ├── cart.js # cart module + └── products.js # products module + ``` + +4. **Use Action Composition** + ```javascript + actions: { + async actionA ({ commit }) { + commit('gotData', await getData()) + }, + async actionB ({ dispatch, commit }) { + await dispatch('actionA') // wait for actionA to finish + commit('gotOtherData', await getOtherData()) + } + } + ``` + +## Migration from Vuex 3 to 4 + +1. **Installation** + ```bash + npm install vuex@next + ``` + +2. **Breaking Changes** + - Vue 3 compatibility only + - New `createStore` function instead of `new Vuex.Store` + - TypeScript improvements + - `store.subscribeAction` now receives an object with `before` and `after` hooks + +## Common Pitfalls + +1. **Mutating State Outside Mutations** + ```javascript + // Bad + store.state.count = 10 + + // Good + store.commit('setCount', 10) + ``` + +2. **Not Using Getters for Derived State** + ```javascript + // Bad - computed property in component + computed: { + completedTodos() { + return this.$store.state.todos.filter(todo => todo.completed) + } + } + + // Good - using getter + computed: { + completedTodos() { + return this.$store.getters.completedTodos + } + } + ``` + +3. **Overusing Vuex** + - Use component local state for component-specific state + - Use props/events for parent-child communication + - Use provide/inject for dependency injection + - Use Vuex for global state that needs to be shared across components + +## Performance Optimization + +1. **Use `mapState` with Care** + ```javascript + // Instead of this (creates a new computed property for each state property) + ...mapState(['a', 'b', 'c']) + + // Do this (single computed property) + ...mapState({ + state: state => ({ + a: state.a, + b: state.b, + c: state.c + }) + }) + ``` + +2. **Use `mapGetters` for Multiple Getters** + ```javascript + import { mapGetters } from 'vuex' + + export default { + computed: { + ...mapGetters([ + 'doneTodosCount', + 'anotherGetter', + // ... + ]) + } + } + ``` + +3. **Lazy Load Modules** + ```javascript + const store = createStore({ /* ... */ }) + + // Load module dynamically + import('./modules/cart').then(cartModule => { + store.registerModule('cart', cartModule.default) + }) + ``` + +## Useful Plugins + +1. **vuex-persistedstate** + ```bash + npm install --save vuex-persistedstate + ``` + + ```javascript + import createPersistedState from 'vuex-persistedstate' + + const store = createStore({ + // ... + plugins: [ + createPersistedState({ + key: 'my-app', + paths: ['user', 'settings'] + }) + ] + }) + ``` + +2. **vuex-router-sync** + ```bash + npm install vuex-router-sync@next + ``` + + ```javascript + import { sync } from 'vuex-router-sync' + import store from './store' + import router from './router' + + sync(store, router) + ``` + +3. **vuex-shared-mutations** + ```bash + npm install --save vuex-shared-mutations + ``` + + ```javascript + import createMutationsSharer from 'vuex-shared-mutations' + + const store = createStore({ + // ... + plugins: [ + createMutationsSharer({ + predicate: [ + 'increment', + 'decrement', + 'setUser' + ] + }) + ] + }) + ``` + +## Conclusion + +Vuex provides a robust solution for state management in Vue.js applications. While newer solutions like Pinia offer a more modern API, Vuex remains a solid choice, especially for existing projects. By following the patterns and best practices outlined in this guide, you can build maintainable and scalable Vue.js applications with Vuex.