The AgriChain backend provides RESTful APIs for the smart farming dashboard, handling IoT device data, blockchain transactions, AI predictions, and user management.
# Clone the repository
git clone https://github.com/agrichain/backend.git
cd backend
# Install dependencies
npm install
# Set up environment variables
cp .env.example .env
# Edit .env with your configuration
# Start the development server
npm run dev
# For production
npm run build
npm start
// Request Body
{
"name": "John Farmer",
"email": "john@example.com",
"password": "securepassword123",
"farmName": "Green Valley Farms"
}
// Response
{
"success": true,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "507f1f77bcf86cd799439011",
"name": "John Farmer",
"email": "john@example.com",
"farmName": "Green Valley Farms"
}
}
// Request Body
{
"email": "john@example.com",
"password": "securepassword123"
}
// Response (same structure as register)
// Response
{
"success": true,
"devices": [
{
"id": "60d5ec9cfb9d8b3a48fe9a12",
"name": "Soil Moisture Sensor - Field A",
"type": "moisture",
"status": "online",
"batteryLevel": 78,
"signalStrength": 92,
"lastReading": {
"value": 72,
"timestamp": "2023-06-15T08:30:45.000Z"
},
"location": {
"field": "A",
"coordinates": [34.0522, -118.2437]
}
},
// ... more devices
]
}
// Request Body
{
"deviceId": "60d5ec9cfb9d8b3a48fe9a12",
"secretKey": "device-secret-key",
"readings": [
{
"type": "moisture",
"value": 72,
"timestamp": "2023-06-15T08:30:45.000Z"
}
]
}
// Response
{
"success": true,
"message": "Data received and stored",
"blockchainHash": "a3f8e2d4b5c71f9e8a0b6d5c4f3e2a1b"
}
// Response
{
"success": true,
"blocks": [
{
"index": 4892,
"timestamp": "2023-06-15T08:30:45.000Z",
"data": {
"deviceId": "60d5ec9cfb9d8b3a48fe9a12",
"readingType": "moisture",
"value": 72,
"field": "A"
},
"previousHash": "b5c71f9e8a0b6d5c4f3e2a1ba3f8e2d",
"hash": "a3f8e2d4b5c71f9e8a0b6d5c4f3e2a1b"
},
// ... more blocks
]
}
// Response
{
"success": true,
"valid": true,
"invalidBlocks": [],
"message": "Blockchain integrity verified"
}
// Response
{
"success": true,
"predictions": [
{
"type": "irrigation",
"field": "A",
"prediction": "Next irrigation in 8 hours",
"confidence": 0.92,
"recommendation": "Delay watering until evening to reduce evaporation"
},
{
"type": "pest",
"field": "B",
"prediction": "Low pest risk (15%)",
"confidence": 0.87,
"recommendation": "Monitor for corn borer, no treatment needed yet"
},
{
"type": "harvest",
"field": "C",
"prediction": "Optimal harvest window: July 15-18",
"confidence": 0.85,
"recommendation": "Prepare workforce and storage for peak yield"
}
]
}
// Response
{
"success": true,
"message": "AI models retrained successfully",
"newAccuracy": {
"irrigation": 0.93,
"pest": 0.88,
"harvest": 0.86
}
}
// Response
{
"success": true,
"services": [
{
"provider": "AWS IoT",
"status": "connected",
"metrics": {
"messages": 1248,
"storage": 2.8,
"rulesProcessed": 56
}
},
{
"provider": "Azure Data Lake",
"status": "syncing",
"metrics": {
"dataStored": 4.2,
"lastSync": "2023-06-15T08:28:12.000Z",
"analyticsJobs": 12
}
},
{
"provider": "Google BigQuery",
"status": "processing",
"metrics": {
"queries": 84,
"mlModels": 3,
"storage": 1.5
}
}
]
}
{
name: String,
email: { type: String, unique: true },
password: String,
farmName: String,
role: { type: String, enum: ['user', 'admin'], default: 'user' },
createdAt: { type: Date, default: Date.now }
}
{
name: String,
type: { type: String, enum: ['moisture', 'temperature', 'humidity', 'light', 'ph', 'co2'] },
status: { type: String, enum: ['online', 'offline', 'maintenance'] },
batteryLevel: Number,
signalStrength: Number,
secretKey: String, // For authentication
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
location: {
field: String,
coordinates: [Number] // [longitude, latitude]
}
}
{
deviceId: { type: mongoose.Schema.Types.ObjectId, ref: 'Device' },
type: String,
value: Number,
timestamp: { type: Date, default: Date.now },
field: String,
blockchainHash: String
}
{
index: Number,
timestamp: { type: Date, default: Date.now },
data: {
deviceId: { type: mongoose.Schema.Types.ObjectId, ref: 'Device' },
readingType: String,
value: Number,
field: String
},
previousHash: String,
hash: String
}
{
type: { type: String, enum: ['irrigation', 'pest', 'harvest'] },
field: String,
prediction: String,
confidence: Number,
recommendation: String,
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
createdAt: { type: Date, default: Date.now }
}
{
provider: { type: String, enum: ['aws', 'azure', 'google'] },
status: String,
metrics: {
messages: Number,
storage: Number,
rulesProcessed: Number,
// ... other metrics
},
lastSync: Date,
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }
}
require('dotenv').config();
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const { Web3 } = require('web3');
const tf = require('@tensorflow/tfjs-node');
const app = express();
// Database connection
mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then(() => console.log('Connected to MongoDB'))
.catch(err => console.error('MongoDB connection error:', err));
// Middleware
app.use(cors());
app.use(helmet());
app.use(express.json());
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
app.use(limiter);
// Blockchain setup
const web3 = new Web3(process.env.BLOCKCHAIN_NODE_URL);
const contract = new web3.eth.Contract(
JSON.parse(process.env.CONTRACT_ABI),
process.env.CONTRACT_ADDRESS
);
// Routes
app.use('/api/auth', require('./routes/auth'));
app.use('/api/iot', require('./routes/iot'));
app.use('/api/blockchain', require('./routes/blockchain'));
app.use('/api/ai', require('./routes/ai'));
app.use('/api/cloud', require('./routes/cloud'));
// Error handling
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({
success: false,
message: 'Internal server error'
});
});
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
module.exports = app;
const express = require('express');
const router = express.Router();
const auth = require('../middleware/auth');
const Device = require('../models/Device');
const Reading = require('../models/Reading');
const { addBlock } = require('../utils/blockchain');
// @route GET api/iot/devices
// @desc Get all user's IoT devices
// @access Private
router.get('/devices', auth, async (req, res) => {
try {
const devices = await Device.find({ userId: req.user.id });
// Get latest readings for each device
const devicesWithReadings = await Promise.all(devices.map(async device => {
const latestReading = await Reading.findOne({ deviceId: device._id })
.sort({ timestamp: -1 })
.limit(1);
return {
...device.toObject(),
lastReading: latestReading || null
};
}));
res.json({ success: true, devices: devicesWithReadings });
} catch (err) {
console.error(err.message);
res.status(500).json({ success: false, message: 'Server error' });
}
});
// @route POST api/iot/data
// @desc Receive data from IoT device
// @access Private (using device secret key)
router.post('/data', async (req, res) => {
const { deviceId, secretKey, readings } = req.body;
try {
// Verify device
const device = await Device.findOne({ _id: deviceId, secretKey });
if (!device) {
return res.status(401).json({
success: false,
message: 'Invalid device credentials'
});
}
// Save readings
const savedReadings = await Promise.all(readings.map(async reading => {
const newReading = new Reading({
deviceId,
type: reading.type,
value: reading.value,
timestamp: reading.timestamp || Date.now(),
field: device.location.field
});
// Add to blockchain
const blockData = {
deviceId,
readingType: reading.type,
value: reading.value,
field: device.location.field
};
const hash = await addBlock(blockData);
newReading.blockchainHash = hash;
return await newReading.save();
}));
// Update device status and battery if provided
if (readings[0].batteryLevel) {
device.batteryLevel = readings[0].batteryLevel;
await device.save();
}
res.json({
success: true,
message: 'Data received and stored',
blockchainHash: savedReadings[0].blockchainHash
});
} catch (err) {
console.error(err.message);
res.status(500).json({ success: false, message: 'Server error' });
}
});
module.exports = router;
const Block = require('../models/Block');
const crypto = require('crypto');
// Simple blockchain implementation (in production would use Ethereum or similar)
class Blockchain {
constructor() {
this.chain = [];
this.initializeChain();
}
async initializeChain() {
// Load existing chain from database
const blocks = await Block.find().sort({ index: 1 });
this.chain = blocks;
// If no blocks, create genesis block
if (this.chain.length === 0) {
await this.createGenesisBlock();
}
}
async createGenesisBlock() {
const genesisBlock = new Block({
index: 0,
timestamp: Date.now(),
data: { message: "Genesis block" },
previousHash: "0",
hash: this.calculateHash(0, "0", Date.now(), { message: "Genesis block" })
});
await genesisBlock.save();
this.chain.push(genesisBlock);
}
calculateHash(index, previousHash, timestamp, data) {
return crypto
.createHash('sha256')
.update(index + previousHash + timestamp + JSON.stringify(data))
.digest('hex');
}
async addBlock(data) {
const previousBlock = this.chain[this.chain.length - 1];
const newIndex = previousBlock.index + 1;
const newTimestamp = Date.now();
const newHash = this.calculateHash(newIndex, previousBlock.hash, newTimestamp, data);
const newBlock = new Block({
index: newIndex,
timestamp: newTimestamp,
data,
previousHash: previousBlock.hash,
hash: newHash
});
await newBlock.save();
this.chain.push(newBlock);
return newHash;
}
async validateChain() {
for (let i = 1; i < this.chain.length; i++) {
const currentBlock = this.chain[i];
const previousBlock = this.chain[i - 1];
// Check hash
if (currentBlock.hash !== this.calculateHash(
currentBlock.index,
currentBlock.previousHash,
currentBlock.timestamp,
currentBlock.data
)) {
return false;
}
// Check previous hash
if (currentBlock.previousHash !== previousBlock.hash) {
return false;
}
}
return true;
}
}
const blockchain = new Blockchain();
// Public functions
const addBlock = async (data) => {
return await blockchain.addBlock(data);
};
const validateChain = async () => {
return await blockchain.validateChain();
};
const getBlocks = async () => {
return blockchain.chain;
};
module.exports = {
addBlock,
validateChain,
getBlocks
};
# Use official Node.js image
FROM node:18-alpine
# Create app directory
WORKDIR /usr/src/app
# Install app dependencies
COPY package*.json ./
RUN npm install
# Bundle app source
COPY . .
# Build the app
RUN npm run build
# Expose the app port
EXPOSE 5000
# Start the app
CMD ["npm", "start"]
version: '3.8'
services:
app:
build: .
ports:
- "5000:5000"
environment:
- NODE_ENV=production
- MONGODB_URI=mongodb://mongo:27017/agrichain
- JWT_SECRET=your_jwt_secret
depends_on:
- mongo
restart: unless-stopped
mongo:
image: mongo:6.0
ports:
- "27017:27017"
volumes:
- mongodb_data:/data/db
environment:
- MONGO_INITDB_ROOT_USERNAME=root
- MONGO_INITDB_ROOT_PASSWORD=example
volumes:
mongodb_data:
apiVersion: apps/v1
kind: Deployment
metadata:
name: agrichain-backend
spec:
replicas: 3
selector:
matchLabels:
app: agrichain-backend
template:
metadata:
labels:
app: agrichain-backend
spec:
containers:
- name: app
image: your-registry/agrichain-backend:latest
ports:
- containerPort: 5000
env:
- name: NODE_ENV
value: production
- name: MONGODB_URI
value: "mongodb://agrichain-mongo:27017/agrichain"
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: agrichain-secrets
key: jwt-secret
- name: mongo
image: mongo:6.0
ports:
- containerPort: 27017
env:
- name: MONGO_INITDB_ROOT_USERNAME
valueFrom:
secretKeyRef:
name: agrichain-secrets
key: mongo-root-username
- name: MONGO_INITDB_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: agrichain-secrets
key: mongo-root-password
volumeMounts:
- name: mongodb-data
mountPath: /data/db
volumes:
- name: mongodb-data
persistentVolumeClaim:
claimName: mongodb-pvc
---
apiVersion: v1
kind: Service
metadata:
name: agrichain-service
spec:
selector:
app: agrichain-backend
ports:
- protocol: TCP
port: 80
targetPort: 5000
type: LoadBalancer