fsdcats 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,905 @@
1
+ PRACTICAL 1: VUE DIRECTIVES (V-IF/ELSE, V-SHOW, V-FOR, V-ON, V-MODEL)
2
+
3
+
4
+
5
+
6
+ SETUP:
7
+
8
+ 1. Run:
9
+ npm create vue@latest my-vue-p1
10
+
11
+ 2. Terminal Prompts:
12
+ Select No to all options (TypeScript, Router, Pinia, Vitest, ESLint, Prettier).
13
+
14
+ 3. cd my-vue-p1
15
+ npm install
16
+
17
+ 4. npm run dev
18
+ (App runs on http://localhost:5173)
19
+
20
+ 5. Files to create/update:
21
+ - Create src/users.json
22
+ - Replace src/App.vue with the code below.
23
+
24
+
25
+
26
+
27
+
28
+ COMPONENTS:
29
+
30
+
31
+ COMPONENT: users.json
32
+ PATH: src/users.json
33
+
34
+ [
35
+ { "username": "admin", "role": "Admin", "permissions": ["Create", "Edit", "Delete"] },
36
+ { "username": "editor", "role": "Editor", "permissions": ["Create", "Edit"] },
37
+ { "username": "viewer", "role": "Viewer", "permissions": ["Read Only"] }
38
+ ]
39
+
40
+
41
+
42
+
43
+
44
+ COMPONENT: App.vue
45
+ PATH: src/App.vue
46
+
47
+ <script setup>
48
+ import { ref } from 'vue'
49
+ import users from './users.json'
50
+
51
+ const username = ref('')
52
+ const password = ref('')
53
+ const currentUser = ref(null)
54
+ const error = ref(false)
55
+
56
+ function handleLogin() {
57
+ const found = users.find(u => u.username === username.value && password.value === '123')
58
+ if (found) {
59
+ currentUser.value = found
60
+ error.value = false
61
+ username.value = ''
62
+ password.value = ''
63
+ } else {
64
+ error.value = true
65
+ }
66
+ }
67
+
68
+ function handleLogout() {
69
+ currentUser.value = null
70
+ }
71
+ </script>
72
+
73
+ <template>
74
+ <div style="font-family: sans-serif; max-width: 400px; margin: 30px auto; padding: 20px; border: 1px solid #ccc; border-radius: 8px;">
75
+ <!-- 1. v-if / v-else: Toggle between Login and Dashboard -->
76
+ <div v-if="!currentUser">
77
+ <h2>System Login</h2>
78
+ <!-- 2. v-model: Two-way input binding -->
79
+ <p><input v-model="username" placeholder="Username (admin/editor/viewer)" style="width: 100%; padding: 8px;" /></p>
80
+ <p><input v-model="password" type="password" placeholder="Password (123)" style="width: 100%; padding: 8px;" /></p>
81
+
82
+ <!-- 3. v-show: Conditional visibility without DOM removal -->
83
+ <p v-show="error" style="color: red;">Invalid credentials! (Use password: 123)</p>
84
+
85
+ <!-- 4. v-on (@): Event listening -->
86
+ <button @click="handleLogin" style="padding: 8px 16px; background: #2563eb; color: white; border: none; border-radius: 4px; cursor: pointer;">
87
+ Login
88
+ </button>
89
+ </div>
90
+
91
+ <div v-else>
92
+ <h2>Welcome, {{ currentUser.username }}</h2>
93
+ <p>Role: <strong>{{ currentUser.role }}</strong></p>
94
+ <button @click="handleLogout" style="padding: 6px 12px; cursor: pointer;">Logout</button>
95
+
96
+ <h3>Allowed Permissions:</h3>
97
+ <ul>
98
+ <!-- 5. v-for: List iteration with key binding -->
99
+ <li v-for="(perm, index) in currentUser.permissions" :key="index">
100
+ ✅ {{ perm }}
101
+ </li>
102
+ </ul>
103
+ </div>
104
+ </div>
105
+ </template>
106
+
107
+
108
+
109
+
110
+
111
+
112
+
113
+
114
+
115
+
116
+
117
+ PRACTICAL 2: DATA BINDING (PROPS AND EMITS)
118
+
119
+
120
+
121
+
122
+ SETUP:
123
+
124
+ 1. Run:
125
+ npm create vue@latest my-vue-p2
126
+
127
+ 2. Terminal Prompts:
128
+ Select No to all options.
129
+
130
+ 3. cd my-vue-p2
131
+ npm install
132
+
133
+ 4. npm run dev
134
+
135
+ 5. Files to create/update:
136
+ - Create src/DriverCard.vue (Child component)
137
+ - Replace src/App.vue with the code below (Parent component).
138
+
139
+
140
+
141
+
142
+
143
+ COMPONENTS:
144
+
145
+
146
+ COMPONENT: DriverCard.vue
147
+ PATH: src/DriverCard.vue
148
+
149
+ <script setup>
150
+ // 1. defineProps: Receives data from parent
151
+ defineProps({
152
+ name: { type: String, required: true },
153
+ id: { type: Number, required: true }
154
+ })
155
+
156
+ // 2. defineEmits: Declares event to notify parent
157
+ const emit = defineEmits(['remove'])
158
+ </script>
159
+
160
+ <template>
161
+ <div style="display: flex; justify-content: space-between; align-items: center; padding: 10px; margin: 8px 0; background: #f3f4f6; border-radius: 6px;">
162
+ <span>🏎️ {{ name }} <small>(ID: {{ id }})</small></span>
163
+ <!-- Emits 'remove' event passing driver ID -->
164
+ <button @click="emit('remove', id)" style="background: #ef4444; color: white; border: none; padding: 5px 10px; border-radius: 4px; cursor: pointer;">
165
+ Remove
166
+ </button>
167
+ </div>
168
+ </template>
169
+
170
+
171
+
172
+
173
+
174
+ COMPONENT: App.vue
175
+ PATH: src/App.vue
176
+
177
+ <script setup>
178
+ import { ref } from 'vue'
179
+ import DriverCard from './DriverCard.vue'
180
+
181
+ const newDriverName = ref('')
182
+ const drivers = ref([
183
+ { id: 1, name: 'Lewis Hamilton' },
184
+ { id: 2, name: 'Charles Leclerc' },
185
+ { id: 3, name: 'Max Verstappen' }
186
+ ])
187
+
188
+ // Function called when child emits 'remove'
189
+ function removeDriver(id) {
190
+ drivers.value = drivers.value.filter(d => d.id !== id)
191
+ }
192
+
193
+ function addDriver() {
194
+ if (!newDriverName.value.trim()) return
195
+ drivers.value.push({
196
+ id: Date.now(),
197
+ name: newDriverName.value.trim()
198
+ })
199
+ newDriverName.value = ''
200
+ }
201
+ </script>
202
+
203
+ <template>
204
+ <div style="font-family: sans-serif; max-width: 450px; margin: 30px auto; padding: 20px; border: 1px solid #ccc; border-radius: 8px;">
205
+ <h2>Driver Dashboard (Props & Emits)</h2>
206
+
207
+ <!-- Add driver form -->
208
+ <div style="display: flex; gap: 8px; margin-bottom: 16px;">
209
+ <input v-model="newDriverName" placeholder="Enter driver name" style="flex: 1; padding: 8px;" />
210
+ <button @click="addDriver" style="padding: 8px 14px; background: #10b981; color: white; border: none; border-radius: 4px; cursor: pointer;">
211
+ Add
212
+ </button>
213
+ </div>
214
+
215
+ <!--
216
+ - Data Binding DOWN via PROPS: :name and :id
217
+ - Event Binding UP via EMIT: @remove
218
+ -->
219
+ <DriverCard
220
+ v-for="driver in drivers"
221
+ :key="driver.id"
222
+ :id="driver.id"
223
+ :name="driver.name"
224
+ @remove="removeDriver"
225
+ />
226
+
227
+ <p v-if="drivers.length === 0" style="color: #666; font-style: italic;">No drivers in the roster.</p>
228
+ </div>
229
+ </template>
230
+
231
+
232
+
233
+
234
+
235
+
236
+
237
+
238
+
239
+
240
+
241
+ PRACTICAL 3: DOCUMENT UPLOAD & PREVIEW SYSTEM (VUE 3 + EXPRESS + MULTER)
242
+
243
+
244
+
245
+
246
+ SETUP:
247
+
248
+ Backend Setup:
249
+ 1. mkdir backend && cd backend
250
+ 2. npm init -y
251
+ 3. npm install express multer cors
252
+ 4. Create server.js and paste the server.js code below.
253
+ 5. node server.js
254
+ (Runs on http://localhost:5000)
255
+
256
+ Frontend Setup:
257
+ 1. In a new terminal:
258
+ npm create vue@latest frontend
259
+ (Select No to all options)
260
+ 2. cd frontend && npm install
261
+ 3. npm run dev
262
+ (Runs on http://localhost:5173)
263
+ 4. Replace frontend/src/App.vue with the App.vue code below.
264
+
265
+
266
+
267
+
268
+
269
+ COMPONENTS:
270
+
271
+
272
+ COMPONENT: server.js (Backend)
273
+ PATH: backend/server.js
274
+
275
+ const express = require('express');
276
+ const multer = require('multer');
277
+ const cors = require('cors');
278
+ const path = require('path');
279
+ const fs = require('fs');
280
+
281
+ const app = express();
282
+ app.use(cors());
283
+ app.use(express.json());
284
+
285
+ // Ensure uploads directory exists
286
+ const uploadDir = path.join(__dirname, 'uploads');
287
+ if (!fs.existsSync(uploadDir)) fs.mkdirSync(uploadDir);
288
+
289
+ // Serve uploaded files statically for preview/download
290
+ app.use('/uploads', express.static(uploadDir));
291
+
292
+ // Multer Storage Configuration
293
+ const storage = multer.diskStorage({
294
+ destination: (req, file, cb) => cb(null, 'uploads/'),
295
+ filename: (req, file, cb) => cb(null, `${Date.now()}-${file.originalname}`)
296
+ });
297
+ const upload = multer({ storage });
298
+
299
+ let documents = [];
300
+
301
+ // 1. Upload Route
302
+ app.post('/api/documents/upload', upload.single('file'), (req, res) => {
303
+ if (!req.file) return res.status(400).json({ error: 'No file uploaded' });
304
+ const newDoc = {
305
+ id: Date.now(),
306
+ originalName: req.file.originalname,
307
+ filename: req.file.filename,
308
+ size: req.file.size,
309
+ mimeType: req.file.mimetype
310
+ };
311
+ documents.unshift(newDoc);
312
+ res.status(201).json(newDoc);
313
+ });
314
+
315
+ // 2. Fetch All Documents Route
316
+ app.get('/api/documents', (req, res) => {
317
+ res.json(documents);
318
+ });
319
+
320
+ // 3. Delete Document Route
321
+ app.delete('/api/documents/:id', (req, res) => {
322
+ const id = Number(req.params.id);
323
+ const doc = documents.find(d => d.id === id);
324
+ if (!doc) return res.status(404).json({ error: 'File not found' });
325
+
326
+ // Delete file from disk
327
+ const filePath = path.join(uploadDir, doc.filename);
328
+ if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
329
+
330
+ documents = documents.filter(d => d.id !== id);
331
+ res.json({ message: 'Deleted successfully' });
332
+ });
333
+
334
+ app.listen(5000, () => console.log('Backend running on http://localhost:5000'));
335
+
336
+
337
+
338
+
339
+
340
+ COMPONENT: App.vue (Frontend)
341
+ PATH: frontend/src/App.vue
342
+
343
+ <script setup>
344
+ import { ref, onMounted } from 'vue'
345
+
346
+ const selectedFile = ref(null)
347
+ const documents = ref([])
348
+ const loading = ref(false)
349
+
350
+ async function fetchDocuments() {
351
+ const res = await fetch('http://localhost:5000/api/documents')
352
+ documents.value = await res.json()
353
+ }
354
+
355
+ function handleFileSelect(e) {
356
+ selectedFile.value = e.target.files[0]
357
+ }
358
+
359
+ async function handleUpload() {
360
+ if (!selectedFile.value) return
361
+ loading.value = true
362
+
363
+ const formData = new FormData()
364
+ formData.append('file', selectedFile.value)
365
+
366
+ await fetch('http://localhost:5000/api/documents/upload', {
367
+ method: 'POST',
368
+ body: formData
369
+ })
370
+
371
+ selectedFile.value = null
372
+ document.getElementById('fileInput').value = ''
373
+ loading.value = false
374
+ await fetchDocuments()
375
+ }
376
+
377
+ async function handleDelete(id) {
378
+ await fetch(`http://localhost:5000/api/documents/${id}`, { method: 'DELETE' })
379
+ await fetchDocuments()
380
+ }
381
+
382
+ onMounted(fetchDocuments)
383
+ </script>
384
+
385
+ <template>
386
+ <div style="font-family: sans-serif; max-width: 500px; margin: 30px auto; padding: 20px; border: 1px solid #ccc; border-radius: 8px;">
387
+ <h2>Document Upload & Preview</h2>
388
+
389
+ <!-- File input & upload button -->
390
+ <div style="margin-bottom: 20px;">
391
+ <input id="fileInput" type="file" @change="handleFileSelect" />
392
+ <button :disabled="!selectedFile || loading" @click="handleUpload" style="padding: 6px 12px; margin-left: 8px;">
393
+ {{ loading ? 'Uploading...' : 'Upload' }}
394
+ </button>
395
+ </div>
396
+
397
+ <!-- Document List & Preview Link -->
398
+ <h3>Uploaded Documents ({{ documents.length }})</h3>
399
+ <ul style="padding-left: 20px;">
400
+ <li v-for="doc in documents" :key="doc.id" style="margin-bottom: 10px;">
401
+ <a :href="`http://localhost:5000/uploads/${doc.filename}`" target="_blank" style="color: #2563eb; font-weight: bold;">
402
+ {{ doc.originalName }}
403
+ </a>
404
+ <small style="color: #666; margin-left: 8px;">({{ (doc.size / 1024).toFixed(1) }} KB)</small>
405
+ <button @click="handleDelete(doc.id)" style="margin-left: 10px; color: red;">Delete</button>
406
+ </li>
407
+ </ul>
408
+ </div>
409
+ </template>
410
+
411
+
412
+
413
+
414
+
415
+
416
+
417
+
418
+
419
+
420
+
421
+ PRACTICAL 4: LOCALIZED STATE MANAGEMENT (PINIA MULTI-STEP FORM)
422
+
423
+
424
+
425
+
426
+ SETUP:
427
+
428
+ Frontend Setup:
429
+ 1. npm create vue@latest frontend
430
+ (Select YES to Pinia, No to everything else)
431
+ 2. cd frontend && npm install
432
+ 3. npm run dev
433
+ 4. Create src/store.js and replace src/App.vue with the code below.
434
+
435
+ Backend Setup (MongoDB):
436
+ 1. mkdir backend && cd backend
437
+ 2. npm init -y
438
+ 3. npm install express mongoose cors
439
+ 4. Create server.js and paste the server.js code below.
440
+ 5. node server.js
441
+
442
+
443
+
444
+
445
+
446
+ COMPONENTS:
447
+
448
+
449
+ COMPONENT: store.js (Pinia Store)
450
+ PATH: frontend/src/store.js
451
+
452
+ import { defineStore } from 'pinia'
453
+
454
+ export const useApplicationStore = defineStore('application', {
455
+ state: () => ({
456
+ currentStep: 1,
457
+ personal: { name: '', email: '', phone: '' },
458
+ education: { degree: '', college: '', year: '' },
459
+ experience: { company: '', role: '' },
460
+ submitted: false
461
+ }),
462
+ actions: {
463
+ goNext() {
464
+ if (this.currentStep < 4) this.currentStep++
465
+ },
466
+ goBack() {
467
+ if (this.currentStep > 1) this.currentStep--
468
+ },
469
+ async submitApplication() {
470
+ const payload = {
471
+ personal: this.personal,
472
+ education: this.education,
473
+ experience: this.experience
474
+ }
475
+ const res = await fetch('http://localhost:5000/api/applications', {
476
+ method: 'POST',
477
+ headers: { 'Content-Type': 'application/json' },
478
+ body: JSON.stringify(payload)
479
+ })
480
+ if (res.ok) this.submitted = true
481
+ },
482
+ resetForm() {
483
+ this.currentStep = 1
484
+ this.personal = { name: '', email: '', phone: '' }
485
+ this.education = { degree: '', college: '', year: '' }
486
+ this.experience = { company: '', role: '' }
487
+ this.submitted = false
488
+ }
489
+ }
490
+ })
491
+
492
+
493
+
494
+
495
+
496
+ COMPONENT: App.vue (Frontend)
497
+ PATH: frontend/src/App.vue
498
+
499
+ <script setup>
500
+ import { useApplicationStore } from './store.js'
501
+ const store = useApplicationStore()
502
+ </script>
503
+
504
+ <template>
505
+ <div style="font-family: sans-serif; max-width: 450px; margin: 30px auto; padding: 20px; border: 1px solid #ccc; border-radius: 8px;">
506
+ <h2>Job Application Form</h2>
507
+ <p><strong>Step {{ store.currentStep }} of 4</strong></p>
508
+
509
+ <!-- Step 1: Personal Details -->
510
+ <div v-if="store.currentStep === 1">
511
+ <h3>Step 1: Personal Details</h3>
512
+ <p><input v-model="store.personal.name" placeholder="Full Name" style="width: 100%; padding: 8px;" /></p>
513
+ <p><input v-model="store.personal.email" placeholder="Email" style="width: 100%; padding: 8px;" /></p>
514
+ <p><input v-model="store.personal.phone" placeholder="Phone" style="width: 100%; padding: 8px;" /></p>
515
+ </div>
516
+
517
+ <!-- Step 2: Education -->
518
+ <div v-else-if="store.currentStep === 2">
519
+ <h3>Step 2: Education</h3>
520
+ <p><input v-model="store.education.degree" placeholder="Degree (e.g. BSc CS)" style="width: 100%; padding: 8px;" /></p>
521
+ <p><input v-model="store.education.college" placeholder="College Name" style="width: 100%; padding: 8px;" /></p>
522
+ <p><input v-model="store.education.year" placeholder="Passing Year" style="width: 100%; padding: 8px;" /></p>
523
+ </div>
524
+
525
+ <!-- Step 3: Experience -->
526
+ <div v-else-if="store.currentStep === 3">
527
+ <h3>Step 3: Work Experience</h3>
528
+ <p><input v-model="store.experience.company" placeholder="Company Name" style="width: 100%; padding: 8px;" /></p>
529
+ <p><input v-model="store.experience.role" placeholder="Designation / Role" style="width: 100%; padding: 8px;" /></p>
530
+ </div>
531
+
532
+ <!-- Step 4: Review & Submit -->
533
+ <div v-else-if="store.currentStep === 4">
534
+ <h3>Step 4: Review Details</h3>
535
+ <p><strong>Name:</strong> {{ store.personal.name }} | {{ store.personal.email }}</p>
536
+ <p><strong>Degree:</strong> {{ store.education.degree }} ({{ store.education.college }})</p>
537
+ <p><strong>Experience:</strong> {{ store.experience.company }} - {{ store.experience.role }}</p>
538
+
539
+ <button @click="store.submitApplication" style="padding: 10px 20px; background: #16a34a; color: white; border: none; cursor: pointer;">
540
+ Submit Application
541
+ </button>
542
+ <p v-if="store.submitted" style="color: green; font-weight: bold;">✅ Submitted Successfully!</p>
543
+ </div>
544
+
545
+ <!-- Navigation Buttons -->
546
+ <div style="margin-top: 20px; display: flex; justify-content: space-between;">
547
+ <button :disabled="store.currentStep === 1" @click="store.goBack">Back</button>
548
+ <button v-if="store.currentStep < 4" @click="store.goNext">Next</button>
549
+ <button v-if="store.submitted" @click="store.resetForm">Start Over</button>
550
+ </div>
551
+ </div>
552
+ </template>
553
+
554
+
555
+
556
+
557
+
558
+ COMPONENT: server.js (Backend)
559
+ PATH: backend/server.js
560
+
561
+ const express = require('express');
562
+ const mongoose = require('mongoose');
563
+ const cors = require('cors');
564
+
565
+ const app = express();
566
+ app.use(cors());
567
+ app.use(express.json());
568
+
569
+ // MongoDB Application Schema
570
+ const Application = mongoose.model('Application', new mongoose.Schema({
571
+ personal: Object,
572
+ education: Object,
573
+ experience: Object,
574
+ createdAt: { type: Date, default: Date.now }
575
+ }));
576
+
577
+ // POST /api/applications
578
+ app.post('/api/applications', async (req, res) => {
579
+ try {
580
+ const doc = await Application.create(req.body);
581
+ res.status(201).json(doc);
582
+ } catch (err) {
583
+ res.status(400).json({ error: err.message });
584
+ }
585
+ });
586
+
587
+ // GET /api/applications
588
+ app.get('/api/applications', async (req, res) => {
589
+ const docs = await Application.find().sort({ createdAt: -1 });
590
+ res.json(docs);
591
+ });
592
+
593
+ mongoose.connect(process.env.MONGO_URI || 'mongodb://localhost:27017/job_app_db')
594
+ .then(() => app.listen(5000, () => console.log('Server running on port 5000')))
595
+ .catch(err => console.error(err));
596
+
597
+
598
+
599
+
600
+
601
+
602
+
603
+
604
+
605
+
606
+
607
+ PRACTICAL 5: ROUTING IN NUXT AND SHARED LAYOUTS
608
+
609
+
610
+
611
+
612
+ SETUP:
613
+
614
+ 1. Run:
615
+ npx nuxi@latest init nuxt-lab
616
+
617
+ 2. Prompts:
618
+ - Template: minimal
619
+ - Package manager: npm
620
+ - Git repository: No
621
+ - Install dependencies: Yes
622
+
623
+ 3. cd nuxt-lab && npm run dev
624
+ (Runs on http://localhost:3000)
625
+
626
+ 4. Create the following files inside app/ (or root):
627
+ - app/app.vue
628
+ - app/layouts/default.vue
629
+ - app/layouts/minimal.vue
630
+ - app/pages/index.vue
631
+ - app/pages/about.vue
632
+ - app/pages/items/index.vue
633
+ - app/pages/items/[id].vue
634
+
635
+
636
+
637
+
638
+
639
+ COMPONENTS:
640
+
641
+
642
+ COMPONENT: app.vue
643
+ PATH: app/app.vue
644
+
645
+ <!-- app.vue: Wraps the active NuxtLayout and NuxtPage -->
646
+ <template>
647
+ <NuxtLayout>
648
+ <NuxtPage />
649
+ </NuxtLayout>
650
+ </template>
651
+
652
+
653
+
654
+
655
+
656
+ COMPONENT: default.vue (Shared Layout)
657
+ PATH: app/layouts/default.vue
658
+
659
+ <!-- app/layouts/default.vue: Shared Layout with persistent header & footer -->
660
+ <template>
661
+ <div style="font-family: sans-serif; padding: 20px;">
662
+ <header style="background: #f3f4f6; padding: 12px; margin-bottom: 20px;">
663
+ <nav>
664
+ <NuxtLink to="/" style="margin-right: 15px; font-weight: bold;">Home</NuxtLink>
665
+ <NuxtLink to="/items" style="margin-right: 15px; font-weight: bold;">Book Catalogue</NuxtLink>
666
+ <NuxtLink to="/about" style="font-weight: bold;">About</NuxtLink>
667
+ </nav>
668
+ </header>
669
+
670
+ <main>
671
+ <!-- Page content is automatically rendered inside this slot -->
672
+ <slot />
673
+ </main>
674
+
675
+ <footer style="margin-top: 40px; padding-top: 10px; border-top: 1px solid #ddd; color: #666;">
676
+ <p>&copy; 2026 My Nuxt App</p>
677
+ </footer>
678
+ </div>
679
+ </template>
680
+
681
+
682
+
683
+
684
+
685
+ COMPONENT: minimal.vue (Minimal Layout)
686
+ PATH: app/layouts/minimal.vue
687
+
688
+ <!-- app/layouts/minimal.vue: Custom headerless layout -->
689
+ <template>
690
+ <div style="border: 2px dashed #ef4444; padding: 25px; text-align: center; font-family: sans-serif; max-width: 400px; margin: 30px auto;">
691
+ <h3>[Minimal Headerless Layout]</h3>
692
+ <slot />
693
+ <hr style="margin: 20px 0;" />
694
+ <NuxtLink to="/">← Return Home</NuxtLink>
695
+ </div>
696
+ </template>
697
+
698
+
699
+
700
+
701
+
702
+ COMPONENT: index.vue (Home Page)
703
+ PATH: app/pages/index.vue
704
+
705
+ <!-- app/pages/index.vue: Root landing page (Route: /) -->
706
+ <template>
707
+ <div>
708
+ <h1>Welcome to Book Catalogue</h1>
709
+ <p>This is the home page using default layout.</p>
710
+ <NuxtLink to="/items" style="color: #2563eb; font-weight: bold;">View Books →</NuxtLink>
711
+ </div>
712
+ </template>
713
+
714
+
715
+
716
+
717
+
718
+ COMPONENT: about.vue (About Page)
719
+ PATH: app/pages/about.vue
720
+
721
+ <!-- app/pages/about.vue: Route: /about (uses minimal layout) -->
722
+ <script setup>
723
+ // Switch this page to minimal layout instead of default
724
+ definePageMeta({
725
+ layout: 'minimal'
726
+ })
727
+ </script>
728
+
729
+ <template>
730
+ <div>
731
+ <h2>About Us</h2>
732
+ <p>This page uses <code>definePageMeta({ layout: 'minimal' })</code>.</p>
733
+ </div>
734
+ </template>
735
+
736
+
737
+
738
+
739
+
740
+ COMPONENT: items/index.vue (List Page)
741
+ PATH: app/pages/items/index.vue
742
+
743
+ <!-- app/pages/items/index.vue: Route: /items -->
744
+ <script setup>
745
+ const books = [
746
+ { id: 1, title: 'The Hobbit', author: 'J.R.R. Tolkien' },
747
+ { id: 2, title: '1984', author: 'George Orwell' },
748
+ { id: 3, title: 'The Great Gatsby', author: 'F. Scott Fitzgerald' }
749
+ ]
750
+ </script>
751
+
752
+ <template>
753
+ <div>
754
+ <h2>Book Catalogue</h2>
755
+ <ul>
756
+ <li v-for="book in books" :key="book.id" style="margin-bottom: 8px;">
757
+ <!-- Dynamic link to /items/[id] -->
758
+ <NuxtLink :to="`/items/${book.id}`">
759
+ <strong>{{ book.title }}</strong> by {{ book.author }}
760
+ </NuxtLink>
761
+ </li>
762
+ </ul>
763
+ </div>
764
+ </template>
765
+
766
+
767
+
768
+
769
+
770
+ COMPONENT: items/[id].vue (Dynamic Detail Page)
771
+ PATH: app/pages/items/[id].vue
772
+
773
+ <!-- app/pages/items/[id].vue: Dynamic Wildcard Route: /items/:id -->
774
+ <script setup>
775
+ const route = useRoute()
776
+
777
+ const books = [
778
+ { id: 1, title: 'The Hobbit', author: 'J.R.R. Tolkien', desc: 'A fantasy quest across Middle-earth.' },
779
+ { id: 2, title: '1984', author: 'George Orwell', desc: 'A dystopian vision of totalitarian rule.' },
780
+ { id: 3, title: 'The Great Gatsby', author: 'F. Scott Fitzgerald', desc: 'The American jazz age critique.' }
781
+ ]
782
+
783
+ // Extract the id parameter from route
784
+ const bookId = parseInt(route.params.id)
785
+ const book = books.find(b => b.id === bookId)
786
+ </script>
787
+
788
+ <template>
789
+ <div>
790
+ <div v-if="!book">
791
+ <h2>Book Not Found</h2>
792
+ <p>No book exists with ID: {{ route.params.id }}</p>
793
+ <NuxtLink to="/items">Back to Catalogue</NuxtLink>
794
+ </div>
795
+
796
+ <div v-else>
797
+ <h2>{{ book.title }}</h2>
798
+ <h4>Author: {{ book.author }}</h4>
799
+ <p>{{ book.desc }}</p>
800
+ <hr style="margin: 15px 0;" />
801
+ <NuxtLink to="/items">← Back to Catalogue</NuxtLink>
802
+ </div>
803
+ </div>
804
+ </template>
805
+
806
+
807
+
808
+
809
+
810
+
811
+
812
+
813
+
814
+
815
+
816
+ PRACTICAL CRUD: EXPRESS + MONGOOSE REST API
817
+
818
+
819
+
820
+
821
+ SETUP:
822
+
823
+ 1. mkdir crud-backend && cd crud-backend
824
+ 2. npm init -y
825
+ 3. npm install express mongoose
826
+ 4. Create server.js and paste the server.js code below.
827
+ 5. node server.js
828
+ (Runs on http://localhost:5000)
829
+
830
+
831
+
832
+
833
+
834
+ COMPONENTS:
835
+
836
+
837
+ COMPONENT: server.js
838
+ PATH: server.js
839
+
840
+ const express = require('express');
841
+ const mongoose = require('mongoose');
842
+
843
+ const app = express();
844
+ app.use(express.json());
845
+
846
+ // 1. Database Connection
847
+ mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/crud_db')
848
+ .then(() => console.log('✓ MongoDB Connected'))
849
+ .catch(err => console.error('MongoDB Error:', err));
850
+
851
+ // 2. Mongoose Model
852
+ const Item = mongoose.model('Item', new mongoose.Schema({
853
+ name: { type: String, required: true },
854
+ description: String
855
+ }, { timestamps: true }));
856
+
857
+ // 3. CREATE: POST /api/items
858
+ app.post('/api/items', async (req, res) => {
859
+ try {
860
+ const item = await Item.create(req.body);
861
+ res.status(201).json(item);
862
+ } catch (err) {
863
+ res.status(400).json({ error: err.message });
864
+ }
865
+ });
866
+
867
+ // 4. READ ALL: GET /api/items
868
+ app.get('/api/items', async (req, res) => {
869
+ const items = await Item.find().sort({ createdAt: -1 });
870
+ res.json(items);
871
+ });
872
+
873
+ // 5. READ ONE: GET /api/items/:id
874
+ app.get('/api/items/:id', async (req, res) => {
875
+ const item = await Item.findById(req.params.id);
876
+ if (!item) return res.status(404).json({ message: 'Item not found' });
877
+ res.json(item);
878
+ });
879
+
880
+ // 6. UPDATE: PUT /api/items/:id
881
+ app.put('/api/items/:id', async (req, res) => {
882
+ const item = await Item.findByIdAndUpdate(req.params.id, req.body, { new: true });
883
+ if (!item) return res.status(404).json({ message: 'Item not found' });
884
+ res.json(item);
885
+ });
886
+
887
+ // 7. DELETE: DELETE /api/items/:id
888
+ app.delete('/api/items/:id', async (req, res) => {
889
+ const item = await Item.findByIdAndDelete(req.params.id);
890
+ if (!item) return res.status(404).json({ message: 'Item not found' });
891
+ res.json({ message: 'Item deleted successfully' });
892
+ });
893
+
894
+ app.listen(5000, () => console.log('✓ Server running on http://localhost:5000'));
895
+
896
+
897
+
898
+
899
+
900
+
901
+
902
+
903
+
904
+
905
+