Why a Pharmacy Management App?
Inventory management: Tracking stock, expiry dates, and automated reordering.
Prescription handling: Securely managing e-prescriptions and patient history.
Billing and sales: A seamless Point of Sale (POS) system.
Reporting: Generating vital sales and stock reports.
This project is a fantastic opportunity to showcase your full-stack development skills and create something genuinely useful. It’s also a great portfolio piece to help you land your next job.
The Technology Stack: Why Angular, Node.js, and SQL?
Choosing the right tech stack is the first, and most crucial, step. Here's why this combination works so well:
Angular (Frontend): It's a robust, component-based framework that scales beautifully. Angular’s opinionated structure and built-in tools like the CLI make it easy to maintain and test large applications. It also has a huge community and is perfect for building complex, single-page applications.
Node.js (Backend): Built on Chrome’s V8 JavaScript engine, Node.js is incredibly fast and efficient for building APIs. Its non-blocking, event-driven architecture makes it ideal for handling multiple concurrent requests—perfect for a system that needs to manage real-time inventory and sales data. We'll be using the Express.js framework to simplify API development.
SQL Database (Database): For a system like this, data integrity is paramount. Relational databases like MySQL or PostgreSQL are the gold standard for managing structured data. They ensure every transaction is reliable, and the relationships between tables—like
products
,invoices
, andcustomers
—are clearly defined and secure.
First, initialize your project and install the necessary dependencies.
cd pharmacy-backend
npm init -y
npm install express mysql2 cors jsonwebtoken bcryptjs dotenv
server.js
). This will be the heart of your API.server.js
file in your backend
directory. This will handle the main server setup.Create a routes
folder and add the following files:
backend/routes/auth.js
// backend/routes/auth.jsconst express = require('express');const router = express.Router();const pool = require('../db'); // Assuming you create a db.js fileconst bcrypt = require('bcryptjs');const jwt = require('jsonwebtoken');
// A simple way to get a pool instance in other filesconst db = require('../db');
// Login routerouter.post('/login', async (req, res) => { const { username, password } = req.body; try { const [rows] = await db.execute('SELECT * FROM users WHERE username = ?', [username]); if (rows.length === 0) { return res.status(401).json({ message: 'Invalid credentials' }); } const user = rows[0]; const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) { return res.status(401).json({ message: 'Invalid credentials' }); } const token = jwt.sign({ id: user.id, role: user.role }, process.env.SECRET_KEY, { expiresIn: '1h' }); res.json({ token, role: user.role }); } catch (error) { console.error(error); res.status(500).json({ message: 'Server error' }); }});
module.exports = router;
backend/routes/products.js
// backend/routes/products.jsconst express = require('express');const router = express.Router();const db = require('../db'); // Assuming you create a db.js fileconst auth = require('../middleware/auth'); // Assuming you create a middleware
// Get all productsrouter.get('/', async (req, res) => { try { const [rows] = await db.execute('SELECT * FROM products'); res.json(rows); } catch (error) { res.status(500).json({ message: 'Server error' }); }});
// Add a new product (requires admin role)router.post('/', auth('admin'), async (req, res) => { const { name, description, price, stock_quantity, expiry_date } = req.body; try { const [result] = await db.execute( 'INSERT INTO products (name, description, price, stock_quantity, expiry_date) VALUES (?, ?, ?, ?, ?)', [name, description, price, stock_quantity, expiry_date] ); res.status(201).json({ message: 'Product added successfully', productId: result.insertId }); } catch (error) { res.status(500).json({ message: 'Server error' }); }});
// Update a productrouter.put('/:id', auth('admin'), async (req, res) => { const { name, description, price, stock_quantity, expiry_date } = req.body; const { id } = req.params; try { await db.execute( 'UPDATE products SET name = ?, description = ?, price = ?, stock_quantity = ?, expiry_date = ? WHERE id = ?', [name, description, price, stock_quantity, expiry_date, id] ); res.json({ message: 'Product updated successfully' }); } catch (error) { res.status(500).json({ message: 'Server error' }); }});
// Delete a productrouter.delete('/:id', auth('admin'), async (req, res) => { const { id } = req.params; try { await db.execute('DELETE FROM products WHERE id = ?', [id]); res.json({ message: 'Product deleted successfully' }); } catch (error) { res.status(500).json({ message: 'Server error' }); }});
module.exports = router;
backend/routes/sales.js
// backend/routes/sales.jsconst express = require('express');const router = express.Router();const db = require('../db');const auth = require('../middleware/auth');
router.post('/', auth(), async (req, res) => { const { items } = req.body; const userId = req.user.id;
try { // Use a transaction for data integrity await db.beginTransaction(); const totalAmount = items.reduce((sum, item) => sum + (item.price * item.quantity), 0); // Insert a new sale record const [saleResult] = await db.execute( 'INSERT INTO sales (user_id, total_amount) VALUES (?, ?)', [userId, totalAmount] ); const saleId = saleResult.insertId; // Insert each item into sales_items and update product stock for (const item of items) { await db.execute( 'INSERT INTO sales_items (sale_id, product_id, quantity, price_at_sale) VALUES (?, ?, ?, ?)', [saleId, item.id, item.quantity, item.price] );
// Update product stock await db.execute( 'UPDATE products SET stock_quantity = stock_quantity - ? WHERE id = ? AND stock_quantity >= ?', [item.quantity, item.id, item.quantity] ); }
await db.commit(); res.status(201).json({ message: 'Sale completed successfully', saleId });
} catch (error) { await db.rollback(); console.error('Sale transaction failed:', error); res.status(500).json({ message: 'Server error during transaction' }); }});
module.exports = router;
backend/db.js
// backend/db.jsconst mysql = require('mysql2/promise');require('dotenv').config();
const pool = mysql.createPool({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME});
module.exports = pool;
backend/middleware/auth.js
const jwt = require('jsonwebtoken');
module.exports = (requiredRole) => (req, res, next) => { try { const token = req.headers.authorization.split(" ")[1]; if (!token) { return res.status(403).json({ message: 'No token provided' }); } const decodedToken = jwt.verify(token, process.env.SECRET_KEY); req.user = decodedToken; if (requiredRole && req.user.role !== requiredRole) { return res.status(403).json({ message: 'Access denied' }); }
next(); } catch (error) { res.status(401).json({ message: 'Invalid token' }); }};
Step 4: Frontend Setup (Angular)
pharmacy-fullstack
directory, and create the Angular app.Step 6: Routing and Components
Configure your Angular router in frontend/src/app/app-routing.module.ts
.
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LoginComponent } from './auth/login/login.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { ProductListComponent } from './products/product-list/product-list.component';
import { AddProductComponent } from './products/add-product/add-product.component';
import { PosComponent } from './sales/pos/pos.component';
import { AuthGuard } from './guards/auth.guard'; // Create this guard
const routes: Routes = [
{ path: 'login', component: LoginComponent },
{ path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] },
{ path: 'products', component: ProductListComponent, canActivate: [AuthGuard] },
{ path: 'add-product', component: AddProductComponent, canActivate: [AuthGuard] },
{ path: 'pos', component: PosComponent, canActivate: [AuthGuard] },
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
{ path: '**', redirectTo: '/dashboard' }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
This is a comprehensive, step-by-step guide to building the complete application. . The code provides the core logic and structure for the backend and frontend. You will need to build out the UI for each component, but the foundation is now in place.
0 Comments