fsdcats 0.0.0-stage → 1.0.1

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