Complete Guide: Integrating Odoo ERP with External Apps 🔧
Okay so Odoo — if you haven't heard of it, it's basically the GOAT for SMEs managing their business operations. The best part? You can connect it with external apps to unlock even more power.
Why Even Use Odoo?
- Modular as hell — CRM, eCommerce, inventory, accounting, all in one. Pick what you need, ignore the rest.
- Open source — customize everything to fit your specific business needs.
- User-Friendly — the UI is actually intuitive, no CS degree required.
Why Bother Integrating with External Apps?
- Work smarter, not harder — automation = less manual data entry = fewer mistakes
- Extend functionality — connect to eCommerce platforms, payment gateways, you name it
- Single source of truth — all data in one place instead of scattered everywhere
- Better team collaboration — everyone on the same page (literally)
- Scale up — grow your tech stack without breaking what already works
What Can You Actually Connect?
- eCommerce: Shopify, WooCommerce, Magento
- Payments: PayPal, Stripe, GoPay
- Marketing: Mailchimp, HubSpot
- Support: Zendesk, Freshdesk
Understanding JSON-RPC
Before we dive in, let's talk JSON-RPC. It's basically a communication protocol that lets apps call functions from other apps. Lightweight, easy to implement, and works with basically any language.
Tutorial: NestJS + Odoo Integration
Here's the real deal — how to send data from a NestJS app to Odoo.
1. Project Structure
├── controllers
│ └── odoo.controller.ts
├── odoo.module.ts
├── services
│ └── odoo.service.ts
2. The OdooService
import { Injectable, HttpException, HttpStatus, Logger } from '@nestjs/common';
import axios from 'axios';
@Injectable()
export class OdooService {
private readonly url = 'http://localhost:8069';
private readonly db = 'odoo-17';
private readonly username = 'admin@admin.com';
private readonly password = 'admin';
private async jsonRpcCall(
method: string,
params: Record<string, unknown>,
): Promise<unknown> {
try {
const response = await axios.post(`${this.url}/jsonrpc`, {
jsonrpc: '2.0',
method,
params,
id: new Date().getTime(),
});
if (response.data.error) {
throw new HttpException(
response.data.error,
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
return response.data.result;
} catch (error) {
throw new HttpException(
error.message,
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
private async authenticate(): Promise<unknown> {
return this.jsonRpcCall('call', {
service: 'common',
method: 'login',
args: [this.db, this.username, this.password],
});
}
private async execute(
model: string,
method: string,
args: any[],
): Promise<unknown> {
const uid = await this.authenticate();
return this.jsonRpcCall('call', {
service: 'object',
method: 'execute_kw',
args: [this.db, uid, this.password, model, method, args],
});
}
// CRUD operations
async create(model: string, data: any): Promise<unknown> {
return this.execute(model, 'create', [data]);
}
async read(model: string, domain: any, fields: any): Promise<unknown> {
return this.execute(model, 'search_read', [domain, { fields }]);
}
async update(model: string, id: number, data: any): Promise<unknown> {
return this.execute(model, 'write', [[id], data]);
}
async delete(model: string, id: number): Promise<unknown> {
return this.execute(model, 'unlink', [[id]]);
}
}
3. The Controller
import {
Controller,
Get,
Post,
Put,
Delete,
Body,
Param,
} from '@nestjs/common';
import { OdooService } from '../services/odoo.service';
@Controller('odoo')
export class OdooController {
constructor(private readonly odooService: OdooService) {}
@Post(':model')
async create(
@Param('model') model: string,
@Body() data: any,
): Promise<unknown> {
return this.odooService.create(model, data);
}
@Get(':model')
async read(
@Param('model') model: string,
@Body() body: any,
): Promise<unknown> {
const { domain, fields } = body;
return this.odooService.read(model, domain, fields);
}
@Put(':model/:id')
async update(
@Param('model') model: string,
@Param('id') id: number,
@Body() data: any,
): Promise<unknown> {
return this.odooService.update(model, id, data);
}
@Delete(':model/:id')
async delete(
@Param('model') model: string,
@Param('id') id: number,
): Promise<unknown> {
return this.odooService.delete(model, id);
}
}
Real-Time Updates? Webhooks Got You
Want to trigger something in your app when action happens in Odoo? Webhooks are your friend.
Custom Module Structure
odoo/addons/project_task_webhook/
├── __init__.py
├── __manifest__.py
└── models/
├── __init__.py
└── project_task.py
project_task.py
import requests
import logging
from odoo import models, fields, api
_logger = logging.getLogger(__name__)
class ProjectTask(models.Model):
_inherit = 'project.task'
@api.model
def create(self, vals):
record = super(ProjectTask, self).create(vals)
self._send_webhook(record)
return record
def _send_webhook(self, record):
url = "http://localhost:3000/api/v1/notifications/send"
data = {
"id": record.id,
"name": record.name,
"description": record.description,
}
headers = {'Content-Type': 'application/json'}
try:
response = requests.post(url, json=data, headers=headers)
response.raise_for_status()
_logger.info('Webhook sent successfully')
except requests.exceptions.RequestException as err:
_logger.error('Webhook failed: %s', err)
TL;DR
Here's what we covered:
- What Odoo is and why it's fire 🔥
- Sending data from external app to Odoo — JSON-RPC API calls
- Reading data from Odoo — GET endpoints with filtering
- Getting real-time triggers — webhooks for the win
- Building custom modules — override Odoo behavior like a pro
For the full code, hit up my GitHub repo. Now go build something awesome!