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