DEV Community

Daniel Ioni
Daniel Ioni

Posted on

🐾 Building a Complete Animal Registration System with Node.js

🐾 Building a Complete Animal Registration System with Node.js
 and MongoDB
Introduction

In this article, I'll walk you through how I built a complete animal registration and management system for MyZubster - a project that aims to create a decentralized global map of plants and animals, powered by the Monero blockchain.
📌 Table of Contents

System Architecture

Data Model

API Endpoints

Authentication & Authorization

Controller Implementation

Testing & Validation

Next Steps
Enter fullscreen mode Exit fullscreen mode

System Architecture

The system follows an MVC (Model-View-Controller) architecture using:

Node.js + Express for the backend

MongoDB + Mongoose for the database

JWT for authentication

bcrypt for password hashing
Enter fullscreen mode Exit fullscreen mode

text

src/
├── models/
│ └── Animal.js # Data model
├── controllers/
│ └── animalController.js # Business logic
├── routes/
│ └── animalRoutes.js # Endpoint definitions
└── middleware/
└── auth.js # JWT authentication

Data Model

Here's the complete model for animal registration:
javascript

const AnimalSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true
},
species: {
type: String,
required: true,
trim: true
},
breed: {
type: String,
trim: true
},
age: {
type: Number,
min: 0,
max: 100
},
gender: {
type: String,
enum: ['male', 'female', 'other'],
default: 'other'
},
color: {
type: String,
trim: true
},
microchipNumber: {
type: String,
trim: true,
unique: true,
sparse: true
},
owner: {
type: String,
required: true,
trim: true
},
ownerEmail: {
type: String,
trim: true,
lowercase: true
},
ownerPhone: {
type: String,
trim: true
},
registeredBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
location: {
lat: { type: Number },
lng: { type: Number },
address: { type: String, trim: true },
city: { type: String, trim: true },
country: { type: String, trim: true }
},
verified: {
type: Boolean,
default: false
},
verifiedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
verifiedAt: {
type: Date
},
imageUrl: {
type: String
},
images: [{
url: String,
caption: String,
uploadedAt: { type: Date, default: Date.now }
}],
notes: {
type: String,
trim: true
},
healthStatus: {
type: String,
enum: ['healthy', 'treatment', 'critical', 'unknown'],
default: 'unknown'
},
vaccinations: [{
name: String,
date: Date,
nextDue: Date,
notes: String
}],
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
}
});

Performance Indexes:
javascript

AnimalSchema.index({ name: 'text', species: 'text', breed: 'text', owner: 'text' });
AnimalSchema.index({ location: '2dsphere' });
AnimalSchema.index({ species: 1 });
AnimalSchema.index({ owner: 1 });
AnimalSchema.index({ verified: 1 });
AnimalSchema.index({ createdAt: -1 });

API Endpoints
Endpoint Method Auth Description
/api/animals GET No List animals with filters & pagination
/api/animals/stats GET No Animal statistics
/api/animals/:id GET No Animal details
/api/animals/register POST JWT Register a new animal
/api/animals/:id PUT JWT Update animal
/api/animals/:id DELETE JWT Delete animal
/api/animals/:id/verify PATCH Admin Verify animal
Authentication & Authorization
javascript

// JWT Authentication Middleware
exports.authenticate = async (req, res, next) => {
try {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({
success: false,
message: 'Authentication token required'
});
}

const token = authHeader.split(' ')[1];
const decoded = jwt.verify(token, process.env.JWT_SECRET);

req.userId = decoded.userId;
req.userRole = decoded.role;
req.username = decoded.username;

next();
Enter fullscreen mode Exit fullscreen mode

} catch (error) {
res.status(401).json({
success: false,
message: 'Invalid or expired token'
});
}
};

// Admin Authorization Middleware
exports.isAdmin = (req, res, next) => {
if (req.userRole !== 'admin') {
return res.status(403).json({
success: false,
message: 'Admin privileges required'
});
}
next();
};

Controller Implementation
Animal Registration
javascript

exports.register = async (req, res) => {
try {
const {
name, species, breed, age, gender, color,
microchipNumber, owner, ownerEmail, ownerPhone,
location, imageUrl, notes, healthStatus
} = req.body;

// Validation
if (!name || !species || !owner) {
  return res.status(400).json({
    success: false,
    message: 'Name, species, and owner are required'
  });
}

// Check for duplicate microchip
if (microchipNumber) {
  const existing = await Animal.findOne({ microchipNumber });
  if (existing) {
    return res.status(400).json({
      success: false,
      message: 'Microchip already registered'
    });
  }
}

const animal = new Animal({
  name,
  species,
  breed,
  age,
  gender,
  color,
  microchipNumber,
  owner,
  ownerEmail,
  ownerPhone,
  location,
  imageUrl,
  notes,
  healthStatus,
  registeredBy: req.userId
});

await animal.save();

res.status(201).json({
  success: true,
  message: 'Animal registered successfully',
  data: animal
});
Enter fullscreen mode Exit fullscreen mode

} catch (error) {
console.error('Registration error:', error);
res.status(500).json({
success: false,
message: 'Error registering animal',
error: error.message
});
}
};

Search with Filters
javascript

exports.getAll = async (req, res) => {
try {
const {
species, breed, owner, verified,
search, limit = 50, page = 1
} = req.query;

const query = {};
if (species) query.species = species;
if (breed) query.breed = breed;
if (owner) query.owner = { $regex: owner, $options: 'i' };
if (verified !== undefined) query.verified = verified === 'true';

// Full-text search
if (search) {
  query.$text = { $search: search };
}

const skip = (parseInt(page) - 1) * parseInt(limit);
const animals = await Animal.find(query)
  .sort(search ? { score: { $meta: 'textScore' } } : { createdAt: -1 })
  .skip(skip)
  .limit(parseInt(limit))
  .populate('registeredBy', 'username email')
  .populate('verifiedBy', 'username email');

const total = await Animal.countDocuments(query);

res.json({
  success: true,
  count: animals.length,
  total,
  page: parseInt(page),
  totalPages: Math.ceil(total / parseInt(limit)),
  data: animals
});
Enter fullscreen mode Exit fullscreen mode

} catch (error) {
console.error('Get animals error:', error);
res.status(500).json({
success: false,
message: 'Error retrieving animals',
error: error.message
});
}
};

Statistics
javascript

exports.getStats = async (req, res) => {
try {
const total = await Animal.countDocuments();
const verified = await Animal.countDocuments({ verified: true });

const species = await Animal.aggregate([
  { $group: { _id: '$species', count: { $sum: 1 } } },
  { $sort: { count: -1 } },
  { $limit: 10 }
]);

res.json({
  success: true,
  data: {
    total,
    verified,
    unverified: total - verified,
    topSpecies: species
  }
});
Enter fullscreen mode Exit fullscreen mode

} catch (error) {
console.error('Stats error:', error);
res.status(500).json({
success: false,
message: 'Error retrieving statistics'
});
}
};

Testing
Register an Animal
bash

First, login to get a JWT token

TOKEN=$(curl -s -X POST http://localhost:10000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"password123"}' \
| jq -r '.data.token')

Register an animal

curl -X POST http://localhost:10000/api/animals/register \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Fido",
"species": "Dog",
"breed": "Golden Retriever",
"age": 3,
"gender": "male",
"color": "Golden",
"owner": "John Doe",
"ownerEmail": "john@example.com",
"location": {
"lat": 41.9028,
"lng": 12.4964,
"city": "Rome",
"country": "Italy"
}
}' | jq '.'

List Animals with Filters
bash

Get all animals

curl "http://localhost:10000/api/animals" | jq '.'

Search by species

curl "http://localhost:10000/api/animals?species=Dog" | jq '.'

Full-text search

curl "http://localhost:10000/api/animals?search=Fido" | jq '.'

Get statistics

curl "http://localhost:10000/api/animals/stats" | jq '.'

Animal Verification (Admin Only)
bash

Verify an animal (admin required)

curl -X PATCH http://localhost:10000/api/animals/{animalId}/verify \
-H "Authorization: Bearer $ADMIN_TOKEN" | jq '.'

Key Features Implemented

✅ Full CRUD Operations - Create, Read, Update, Delete

✅ JWT Authentication - Secure API endpoints

✅ Role-Based Access - User vs Admin permissions

✅ Advanced Search - Full-text search with filters

✅ Geolocation - Location data with MongoDB 2dsphere indexes

✅ Pagination - Efficient data loading

✅ Statistics - Aggregated animal data

✅ Microchip Tracking - Unique identification

✅ Verification System - Admin approval workflow

✅ Image Support - Multiple images per animal

✅ Health Status - Track animal health conditions

✅ Vaccination Records - Track vaccinations and due dates
Enter fullscreen mode Exit fullscreen mode

Next Steps

Plant Registration - Build similar system for plants

Monero Payments - Integrate cryptocurrency payments

Frontend Integration - React Native mobile app

Real-time Updates - WebSocket for live data

Mobile App - Build the Expo/React Native app

Monero Wallet - Integrate payment gateway

Global Map - Visualize animals on an interactive map
Enter fullscreen mode Exit fullscreen mode

Tech Stack Summary
Technology Purpose
Node.js Backend runtime
Express Web framework
MongoDB Database
Mongoose ODM for MongoDB
JWT Authentication
bcrypt Password hashing
PM2 Process management
Repository

Check out the full source code on GitHub:

Project: MyZubster Gateway

App: MyZubster Mobile App
Enter fullscreen mode Exit fullscreen mode

Conclusion

Building this animal registration system was a great experience in creating a robust, secure, and scalable API. The combination of Node.js, Express, and MongoDB with proper authentication and authorization patterns makes it production-ready.

The system is now fully functional and ready for the next phase - integrating the React Native mobile app and the Monero payment gateway.

💚 Built with ❤️ for animals and plants by MyZubster-Ecosystem

Follow the project:

GitHub

Telegram

Twitter/X
Enter fullscreen mode Exit fullscreen mode

Tag: #NodeJS #
 #JavaScript #OpenSource #AnimalRegistry #MyZubster

Top comments (0)