I Built a Full-Stack Invoice App from Scratch. Here's the Complete Breakdown
Source: Dev.to
Most invoice tools are either too expensive or too complicated. I built my own in one week, deployed it live, and documented every technical decision and lesson learned.
Live Demo & Source Code
- Live app: https://invoxa-eta.vercel.app
- GitHub repository: https://github.com/Carter254g/invoxa
Tech Stack
- Frontend: React
- Backend: Node.js with Express
- Database: PostgreSQL
- Deployment: Frontend on Vercel, backend on Render
Authentication Middleware
Every protected route runs through a single middleware before reaching the controller.
// auth.js
const auth = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
return res.status(401).json({ error: 'Invalid or expired token' });
}
};
Clean, reusable, and protects every route with one function.
Automatic Database Migrations
The server creates tables automatically on startup, so anyone who clones the repo gets a working database in seconds.
// server.js
migrate.createTables().then(() => {
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
});
Global Axios Interceptor
Instead of attaching the JWT token to each request manually, an interceptor adds it globally.
// api.js
api.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
Write it once, forget about it.
Deployment Gotchas
- Render SSL for PostgreSQL – Add
rejectUnauthorized: falseto yourpgPool config in production, otherwise connections fail. - CORS configuration – Explicitly list your Vercel domain; a wildcard does not work with credentials.
- JWT expiry values – Environment variables must be strings. If the value is
undefined,jwt.signthrows a silent error. Provide a fallback value.
These issues cost me two hours; now they cost you nothing.
Dashboard Overview
The dashboard displays:
- Total invoices
- Total clients
- Revenue collected
- Outstanding balance
All figures are calculated from live database queries on every load—no fake data or hard‑coded numbers.
Additional Features
- PDF export for invoices
- Email delivery via Nodemailer
- Recurring invoices
- Payment gateway integration
Conclusion
If this breakdown helped you, consider starring the repository on GitHub and leaving a comment.
Tags: javascript, node, react, webdev