Web-based Task Manager app on Rocky Linux 8.7

 Perfect 👍 — let’s put it all together and make a working web-based Task Manager app on Rocky Linux 8.7, using the Express + SQLite code we already fixed. I’ll give you the complete structure, backend (server.js), frontend (index.html), installation steps, and systemd + nginx setup so it runs as a service.


📂 Project Structure

We’ll place the app in /opt/taskmgr:

/opt/taskmgr
│── package.json
│── server.js
│
├── node_modules/     (created by npm install)
└── public/
    └── index.html    (frontend page)

1. Install prerequisites

Run as root:

# Update system
dnf update -y

# Install Node.js 18 (supported in Rocky 8.7)
dnf module reset nodejs -y
dnf module enable nodejs:18 -y
dnf install -y nodejs git nginx sqlite

# Install build tools (optional, for node-gyp if needed later)
dnf groupinstall "Development Tools" -y

2. Create the app directory

mkdir -p /opt/taskmgr
cd /opt/taskmgr
npm init -y

3. Install dependencies

npm install express sqlite3 body-parser cors

4. Backend code (server.js)

Create file /opt/taskmgr/server.js:

// server.js
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const sqlite3 = require('sqlite3').verbose();
const path = require('path');

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

// serve frontend
app.use(express.static(path.join(__dirname, 'public')));

// open DB
const db = new sqlite3.Database('./tasks.db');

// ensure table exists
db.run(`CREATE TABLE IF NOT EXISTS tasks (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  title TEXT NOT NULL,
  done INTEGER DEFAULT 0,
  created_at TEXT DEFAULT (datetime('now')),
  closed_at TEXT
)`);

// list tasks
app.get('/api/tasks', (req, res) => {
  db.all('SELECT * FROM tasks ORDER BY created_at DESC', [], (err, rows) => {
    if (err) return res.status(500).json({ error: err.message });
    res.json(rows);
  });
});

// create task
app.post('/api/tasks', (req, res) => {
  const { title } = req.body;
  if (!title) return res.status(400).json({ error: 'title required' });
  db.run('INSERT INTO tasks (title) VALUES (?)', [title], function(err) {
    if (err) return res.status(500).json({ error: err.message });
    db.get('SELECT * FROM tasks WHERE id = ?', [this.lastID], (err, row) => {
      if (err) return res.status(500).json({ error: err.message });
      res.status(201).json(row);
    });
  });
});

// toggle done
app.patch('/api/tasks/:id/toggle', (req, res) => {
  const id = req.params.id;
  db.get('SELECT done FROM tasks WHERE id = ?', [id], (err, row) => {
    if (err) return res.status(500).json({ error: err.message });
    if (!row) return res.status(404).json({ error: 'not found' });
    const newVal = row.done ? 0 : 1;
    db.run('UPDATE tasks SET done = ? WHERE id = ?', [newVal, id], function(err) {
      if (err) return res.status(500).json({ error: err.message });
      res.json({ id: Number(id), done: newVal });
    });
  });
});

// delete task
app.delete('/api/tasks/:id', (req, res) => {
  const id = req.params.id;
  db.run('DELETE FROM tasks WHERE id = ?', [id], function(err) {
    if (err) return res.status(500).json({ error: err.message });
    res.json({ deleted: Number(id) });
  });
});

const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`Task Manager running on port ${port}`));

5. Frontend (public/index.html)

Create folder /opt/taskmgr/public/

Create /opt/taskmgr/public/index.html:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Task Manager</title>
  <style>
    body { font-family: Arial, sans-serif; margin: 20px; }
    li { margin-bottom: 8px; }
    .done { text-decoration: line-through; color: gray; }
    small { display: block; color: #666; }
  </style>
</head>
<body>
  <h1>Task Manager</h1>
  <form id="taskForm">
    <input id="title" placeholder="New task..." required>
    <button type="submit">Add</button>
  </form>
  <ul id="tasks"></ul>

  <script>
    const api = '/api/tasks';

    async function loadTasks() {
      const res = await fetch(api);
      const tasks = await res.json();
      const ul = document.getElementById('tasks');
      ul.innerHTML = '';
      tasks.forEach(t => {
        const li = document.createElement('li');
        li.innerHTML = `
          <span class="${t.done ? 'done' : ''}">${t.title}</span>
          <br>
          <small>Created: ${t.created_at || '—'} 
          ${t.closed_at ? '| Closed: ' + t.closed_at : ''}</small>
          <button onclick="toggleTask(${t.id})">${t.done ? 'Reopen' : 'Complete'}</button>
          <button onclick="deleteTask(${t.id})">Delete</button>
        `;
        ul.appendChild(li);
      });
    }

    async function addTask(e) {
      e.preventDefault();
      const title = document.getElementById('title').value;
      await fetch(api, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ title }) });
      document.getElementById('title').value = '';
      loadTasks();
    }

    async function toggleTask(id) {
      await fetch(`${api}/${id}/toggle`, { method: 'PATCH' });
      loadTasks();
    }

    async function deleteTask(id) {
      await fetch(`${api}/${id}`, { method: 'DELETE' });
      loadTasks();
    }

    document.getElementById('taskForm').addEventListener('submit', addTask);
    loadTasks();
  </script>
</body>
</html>

6. Test manually

cd /opt/taskmgr
node server.js

Now open in browser:

http://<server-ip>:3000/

7. Run as a service

Create systemd unit /etc/systemd/system/taskmgr.service:

[Unit]
Description=Task Manager Node.js App
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/opt/taskmgr
ExecStart=/usr/bin/node server.js
Restart=on-failure
Environment=PORT=3000

[Install]
WantedBy=multi-user.target

Enable and start:

systemctl daemon-reload
systemctl enable --now taskmgr

8. (Optional) Reverse proxy with Nginx

Create /etc/nginx/conf.d/taskmgr.conf:

server {
    listen 80;
    server_name your-domain.com; # or server IP

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Check and reload:

nginx -t && systemctl reload nginx

Now you can access it via:

http://your-domain.com/

✅ You now have a fully working web-based Task Manager app running on Rocky Linux 8.7, accessible via browser, managed as a service.

Do you want me to also add HTTPS setup with Certbot so it runs securely with SSL?

No comments:

Post a Comment