PHARMACY Management App: ANGULAR Full Stack Project

PHARMACY Management App: 
Your Complete Guide to Building a Full-Stack Project in 2025 💊💻

Introduction:

In this guide, I'm going to walk you through the journey of creating a complete Pharmacy Management App using the powerful combination of Angular, Node.js, and a SQL database. This isn't just a generic tutorial; I'll be sharing the key architectural decisions, best practices, and code snippets that I've found essential for a production-ready application. Let's get started. 🚀
  
PHARMACY Management App: ANGULAR Full Stack Project


Why a Pharmacy Management App?

The healthcare industry is in a massive digital transformation, and pharmacies are at the forefront. A well-built pharmacy management system streamlines everything:

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, and customers—are clearly defined and secure.

1. Setting Up the Backend (Node.js & Express)

First, initialize your project and install the necessary dependencies.

mkdir pharmacy-backend
cd pharmacy-backend
npm init -y
npm install express mysql2 cors jsonwebtoken bcryptjs dotenv


Next, create your server file (server.js). This will be the heart of your API.

// server.js
const express = require('express');
const mysql = require('mysql2');
const cors = require('cors');
require('dotenv').config();

const app = express();
const port = process.env.PORT || 3000;

app.use(cors());
app.use(express.json());

// Create a MySQL connection pool
const pool = mysql.createPool({
    host: process.env.DB_HOST,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_NAME
});

pool.getConnection((err, connection) => {
    if (err) {
        console.error('Database connection failed:', err.stack);
        return;
    }
    console.log('Connected to MySQL database!');
    connection.release(); // Release the connection back to the pool
});

// Basic route
app.get('/', (req, res) => {
    res.send('Welcome to the Pharmacy API!');
});

// Import and use your routes
// const productRoutes = require('./routes/products');
// app.use('/api/products', productRoutes);

app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
});

2. Designing the Database (SQL)

A solid database schema is the backbone of your application. Here's a simplified schema for our project.

-- products table
CREATE TABLE products (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    description TEXT,
    price DECIMAL(10, 2) NOT NULL,
    stock_quantity INT NOT NULL,
    expiry_date DATE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- users table
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    password VARCHAR(255) NOT NULL,
    role ENUM('admin', 'pharmacist') NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- sales table (to track transactions)
CREATE TABLE sales (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT,
    total_amount DECIMAL(10, 2) NOT NULL,
    sale_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id)
);



Step 3: Backend API Development

Create a server.js file in your backend directory. This will handle the main server setup.

// backend/server.js
const express = require('express');
const mysql = require('mysql2');
const cors = require('cors');
require('dotenv').config();

const app = express();
const port = process.env.PORT || 3000;

app.use(cors());
app.use(express.json());

// Database Connection Pool
const pool = mysql.createPool({
    host: process.env.DB_HOST,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_NAME
});

pool.getConnection((err, connection) => {
    if (err) {
        console.error('Database connection failed:', err.stack);
        return;
    }
    console.log('Connected to MySQL database!');
    connection.release();
});

// Import and use routes
const authRoutes = require('./routes/auth');
const productRoutes = require('./routes/products');
const salesRoutes = require('./routes/sales');

app.use('/api/auth', authRoutes);
app.use('/api/products', productRoutes);
app.use('/api/sales', salesRoutes);

app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
});

Create a routes folder and add the following files:

backend/routes/auth.js

// backend/routes/auth.js
const express = require('express');
const router = express.Router();
const pool = require('../db'); // Assuming you create a db.js file
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');

// A simple way to get a pool instance in other files
const db = require('../db');

// Login route
router.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.js
const express = require('express');
const router = express.Router();
const db = require('../db'); // Assuming you create a db.js file
const auth = require('../middleware/auth'); // Assuming you create a middleware

// Get all products
router.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 product
router.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 product
router.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.js
const 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.js
const 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)

Open a new terminal, navigate to the pharmacy-fullstack directory, and create the Angular app.

ng new frontend --skip-install
cd frontend
npm install
Step 5: Frontend Component and Service Creation

Once the setup is complete, generate the necessary components and services.

ng g c auth/login
ng g c dashboard
ng g c products/product-list
ng g c products/add-product
ng g c sales/pos
ng g s services/auth
ng g s services/product

frontend/src/app/services/auth.service.ts

// frontend/src/app/services/auth.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, BehaviorSubject } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  private apiUrl = 'http://localhost:3000/api/auth';
  private loggedIn = new BehaviorSubject<boolean>(false);
  
  constructor(private http: HttpClient) {
    this.loggedIn.next(!!localStorage.getItem('token'));
  }

  get isLoggedIn() {
    return this.loggedIn.asObservable();
  }

  login(credentials: any): Observable<any> {
    return this.http.post<any>(`${this.apiUrl}/login`, credentials).pipe(
      tap(res => {
        localStorage.setItem('token', res.token);
        localStorage.setItem('role', res.role);
        this.loggedIn.next(true);
      })
    );
  }

  logout() {
    localStorage.removeItem('token');
    localStorage.removeItem('role');
    this.loggedIn.next(false);
  }

  getToken(): string | null {
    return localStorage.getItem('token');
  }

  getRole(): string | null {
    return localStorage.getItem('role');
  }
}

frontend/src/app/services/product.service.ts

// frontend/src/app/services/product.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { AuthService } from './auth.service';

@Injectable({
  providedIn: 'root'
})
export class ProductService {
  private apiUrl = 'http://localhost:3000/api/products';

  constructor(private http: HttpClient, private authService: AuthService) { }

  private getAuthHeaders(): HttpHeaders {
    const token = this.authService.getToken();
    return new HttpHeaders().set('Authorization', `Bearer ${token}`);
  }

  getProducts(): Observable<any[]> {
    return this.http.get<any[]>(this.apiUrl, { headers: this.getAuthHeaders() });
  }

  addProduct(product: any): Observable<any> {
    return this.http.post<any>(this.apiUrl, product, { headers: this.getAuthHeaders() });
  }

  updateProduct(id: number, product: any): Observable<any> {
    return this.http.put<any>(`${this.apiUrl}/${id}`, product, { headers: this.getAuthHeaders() });
  }

  deleteProduct(id: number): Observable<any> {
    return this.http.delete<any>(`${this.apiUrl}/${id}`, { headers: this.getAuthHeaders() });
  }
}

Step 6: Routing and Components

Configure your Angular router in frontend/src/app/app-routing.module.ts.

// 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 { }


Conclusion :

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