Doc upload

npm create vue@latest p3
cd p3

Select:
- Vue Router: Yes
- Pinia: Yes


npm install
npm install axios
npm install express mongoose multer cors dotenv
npm install -D nodemon


## 3. Backend setup
mkdir backend
cd backend
npm init -y
npm install express mongoose multer cors dotenv
npm install -D nodemon
mkdir uploads




### backend/package.json
"scripts": {
  "start": "node server.js",
  "dev": "nodemon server.js"
}


### backend/.env
MONGO_URI=mongodb+srv://username:password@cluster.mongodb.net/documents






### backend/server.js
const express = require('express')
const mongoose = require('mongoose')
const multer = require('multer')
const cors = require('cors')
const fs = require('fs')
const path = require('path')
require('dotenv').config()

const app = express()
app.use(cors())
app.use(express.json())

const dir = path.join(__dirname, 'uploads')
app.use('/uploads', express.static(dir))

mongoose.connect(process.env.MONGO_URI)

const Doc = mongoose.model('Document', new mongoose.Schema({
  filename: String,
  storedName: String,
  type: String,
  size: Number,
  date: { type: Date, default: Date.now }
}))

const storage = multer.diskStorage({
  destination: dir,
  filename: (req, file, cb) =>
    cb(null, Date.now() + path.extname(file.originalname))
})

const upload = multer({
  storage,
  limits: { fileSize: 10 * 1024 * 1024 }
})

// Upload document
app.post('/api/docs', upload.single('file'), async (req, res) => {
  try {
    if (!req.file) return res.status(400).json({ error: 'Select a file' })

    const doc = await Doc.create({
      filename: req.file.originalname,
      storedName: req.file.filename,
      type: req.file.mimetype,
      size: req.file.size
    })

    res.json(doc)
  } catch (err) {
    if (req.file) fs.unlink(req.file.path, () => {})
    res.status(500).json({ error: err.message })
  }
})

// Get all documents
app.get('/api/docs', async (req, res) => {
  res.json(await Doc.find().sort({ date: -1 }))
})

// Delete document
app.delete('/api/docs/:id', async (req, res) => {
  try {
    const doc = await Doc.findByIdAndDelete(req.params.id)
    if (!doc) return res.status(404).json({ error: 'Not found' })

    const file = path.join(dir, doc.storedName)
    if (fs.existsSync(file)) fs.unlinkSync(file)

    res.json({ message: 'Document deleted' })
  } catch (err) {
    res.status(500).json({ error: err.message })
  }
})

app.listen(5000, () => console.log('Server running on port 5000'))







### src/stores/documentStore.js
import { defineStore } from 'pinia'
import axios from 'axios'
const api = 'http://localhost:5000/api/docs'
export const useDocumentStore = defineStore('documents', {
  state: () => ({ documents: [] }),
  actions: {
    async fetchDocs() {
      const res = await axios.get(api)
      this.documents = res.data
    },
    async upload(file) {
      const data = new FormData()
      data.append('file', file)
      await axios.post(api, data)
      this.fetchDocs()
    },
    async deleteDoc(id) {
      await axios.delete(`${api}/${id}`)
      this.fetchDocs()
    }
  }
})






### src/components/Upload.vue
<script setup>
import { ref } from 'vue'
import { useDocumentStore } from '../stores/documentStore'
const store = useDocumentStore()
const file = ref(null)
function selectFile(e) {
  file.value = e.target.files[0]
}
async function upload() {
  if (!file.value) return alert('Select a file')
  await store.upload(file.value)
  file.value = null
  alert('Uploaded successfully!')
}
</script>
<template>
  <div class="upload">
    <h2>Upload Document</h2>
    <input type="file" @change="selectFile">
    <button @click="upload">Upload</button>
  </div>
</template>




### src/components/DocumentList.vue
<script setup>
import { useDocumentStore } from '../stores/documentStore'
const store = useDocumentStore()
defineEmits(['select'])
</script>
<template>
  <div class="list">
    <h2>Uploaded Documents</h2>
    <div v-for="doc in store.documents" :key="doc._id"
         class="item">
      <span @click="$emit('select', doc)">
        {{ doc.filename }}
        <small>{{ (doc.size / 1024).toFixed(1) }} KB</small>
      </span>

      <button class="delete" @click="store.deleteDoc(doc._id)">
        Delete
      </button>
    </div>
    <p v-if="!store.documents.length">No documents uploaded.</p>
  </div>
</template>






### src/views/Home.vue
<script setup>
import { ref, onMounted } from 'vue'
import { useDocumentStore } from '../stores/documentStore'
import Upload from '../components/Upload.vue'
import DocumentList from '../components/DocumentList.vue'
const store = useDocumentStore()
const selected = ref(null)
const base = 'http://localhost:5000/uploads/'
onMounted(() => store.fetchDocs())
</script>
<template>
  <div class="container">
    <h1>Document Manager</h1>
    <p>Upload and manage your documents</p>
    <Upload />
    <DocumentList @select="selected = $event" />
    <div v-if="selected" class="preview">
      <h2>{{ selected.filename }}</h2>
      <p>Type: {{ selected.type }}</p>
      <p>Size: {{ (selected.size / 1024).toFixed(1) }} KB</p>
      <p>Date: {{ new Date(selected.date).toLocaleString() }}</p>
      <iframe
        v-if="selected.type === 'application/pdf'"
        :src="base + selected.storedName">
      </iframe>
      <a v-else :href="base + selected.storedName" target="_blank">
        Open Document
      </a>
    </div>
  </div>
</template>





### src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', component: Home }
  ]
})
export default router




### src/App.vue
<template>
  <router-view />
</template>




### src/main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import router from './router'
import App from './App.vue'
import './style.css'
createApp(App)
  .use(createPinia())
  .use(router)
  .mount('#app')




### src/style.css
* { box-sizing: border-box; }
body {
  margin: 0;
  font-family: Arial, sans-serif;
  background: #eef2f7;
  color: #1e293b;
}
.container {
  max-width: 850px;
  margin: 40px auto;
  padding: 25px;
  background: white;
  border-radius: 12px;
  box-shadow: 0 5px 20px #0001;
}
h1 { color: #2563eb; }
.upload, .list, .preview {
  padding: 20px;
  margin: 20px 0;
  background: #f8fafc;
  border-radius: 10px;
}
input, button {
  padding: 10px;
  margin: 8px;
  border-radius: 6px;
}
button {
  background: #2563eb;
  color: white;
  border: none;
  cursor: pointer;
}
.item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12px;
  margin: 8px 0;
  background: white;
  border: 1px solid #ddd;
  border-radius: 7px;
}
.item span { cursor: pointer; }
small { display: block; color: #64748b; }
.delete { background: #dc2626; }
iframe {
  width: 100%;
  height: 450px;
  border: 1px solid #ddd;
  margin-top: 15px;
}
a { color: #2563eb; }




Terminal 1 – Backend
cd p3/backend
npm run dev

Terminal 2 – Frontend
cd p3
npm run dev