This library helps make Epicor calls from a Node application. This library is not official and has no direct relationship with Epicor (c). It is merely a helper library maintained by the community.
npm i --save epicor-rest-node
import { EpicorRestService, EpicorRestVersion } from 'epicor-rest-node';
import { EpicorLicenseType } from 'epicor-rest-node/dist/models/EpicorLicenseType';
// For single instance creation
let EpicorRest = new EpicorRestService();
// or injected into another class/service such as a controller
constructor(private readonly epicorSvc: EpicorService) {}
// Instance properties required to function
EpicorRest.AppPoolHost = 'subdomain.domain.tld';
EpicorRest.AppPoolInstance = 'Epicor10Instance';
EpicorRest.UserName = 'MyEpicorUserName';
EpicorRest.Password = 'MyEpicorPassword';
EpicorRest.APIKey = 'xxxxxxxxxxxxxxxxxxxxxxxxx'; //Needed for V2
EpicorRest.Company = 'EPIC01';
EpicorRest.EpicorRestVersion = EpicorRestVersion.V2; //Defaults to V2
EpicorRest.License = EpicorLicenseType.WebService; //Defaults to Default
@Injectable({ scope: Scope.REQUEST })
tells Nest to treat your service class as a provider with request scope. In practical terms:
- @Injectable(): Marks the class so Nest can manage and inject it wherever needed (dependency injection).
- scope: Scope.REQUEST: Tells Nest to create a new instance of this class for each incoming request, rather than reusing a single global instance. This is useful if each request needs its own state or dependencies. (NOTE: This will also consume additional licenses in Epicor)
import { EpicorRestService } from 'epicor-rest-node';
// Adding REQUEST scope will force a new instance per request made vs a singleton
@Injectable({scope: Scope.REQUEST})
export class EpicorService extends EpicorRestService implements OnModuleInit {
public Plant: string = "MfgSys";
/**
* Constructor
*/
constructor(
private readonly config: ConfigService
) {
super();
this.AppPoolHost = 'subdomain.domain.tld';
this.AppPoolInstance = 'Epicor10Instance';
this.UserName = 'MyEpicorUserName';
this.Password = 'MyEpicorPassword';
this.APIKey = 'xxxxxxxxxxxxxxxxxxxxxxxxx'; //Needed for V2
this.Company = 'EPIC01';
this.EpicorRestVersion = EpicorRestVersion.V2; //Defaults to V2
this.License = EpicorLicenseType.WebService; //Defaults to Default
// TODO: Do we need to implement timezone offset
this.CallSettings = new CallSettings(this.Company, this.Plant, '', '', '');
console.log('My Custom EpicorService Constructor');
}
.
.
.
/**
* Switch Employee ID
* @param empID
*/
public async switchEmployee(empID: string) {
let switchRes = await this.BoPost("Ice.Lib.SessionModSvc", "SetEmployee", { employeeID: empID });
return switchRes;
}
.
.
.
}
let params = new Map<string,string>();
params.set('$filter','ABCCode1 eq \'A\'');
EpicorRest.BoGet('Erp.BO.ABCCodeSvc','ABCCodes',params)?.then(res => {
console.log(res);
}).catch(err => {
console.log(err);
});
EpicorRest.BoPost('Erp.BO.ABCCodeSvc','ABCCodes',data)?.then(res => {
console.log(res);
}).catch(err => {
console.log(err);
});
// Patch and Delete are also available
let params = new Map<string,string>();
params.set('$top','13');
EpicorRest.BaqGet('zCustomer01', params)?.then(res => {
console.log(res);
}).catch(err => {
console.log(err);
});
//BAQ Patch is also available
let smsSend =
{
ToPhone:'123456789',
ToMsg:'Zup from Node'
};
EpicorRest.EfxPost('FacilityPaging','SendSMS',smsSend)?.then(res => {
console.log(res);
}).catch(err => {
console.log(err);
});
An Epicor session can be established at any point by invoking EpicorRest.Createsession()
and make sure to kill the session when you are done.
EpicorRest.Createsession().then((success) => {
// Any calls made in here will use the above created session
let params = new Map<string,string>();
params.set('$filter','ABCCode1 eq \'A\'');
EpicorRest.BoGet('Erp.BO.ABCCodeSvc','ABCCodes',params)?.then(res => {
console.log(res);
}).catch(err => {
console.log(err);
}).finally(() => {
EpicorRest.DestroySession();
});
}).catch((ex) => {
console.log(ex);
});
An Epicor session can be killed manually by invoking EpicorRest.DestroySession()
this needs to be done after the last call to the BO/BAQ/EFX etc.
Managing call context as an object can be done by using the EpicorRest CallContext models.
Generate a header with the call context values you want in your client application and send it in the contextheader
header of your request.
{
"Context":{
"BpmData":[
{
"Character01":"FOO",
"Character02":"BAR",
"Checkbox01":true,
"Date01":"2024-12-27"
}
]
}
}
Below is an example controller endpoint that grabs the contextheader
from the request headers and sends it to our EpicorService
that implements the EpicorRestNode
module. It also then takes the call context and sends it back to our client that made the request.
/**
* Post EFX Data
*/
@Post('PostEFX/:library/:method')
async postEfx(
@Param('company') company: string,
@Param('library') library: string,
@Param('method') method: string,
@Body() body: Record<string, any>, // Capture the entire JSON body
@Req() req,
@Res({ passthrough: true }) res: any, // passthrough is important to allow us to send the context headers back but allow interceptors to still run on our 'data' return if we need to
) {
// Pass the body directly as params
const { data, context } = await this.epicorSvc.callEFX(company, library, method, body, req.headers['contextheader']);
// Set the `callcontext` in the response headers to our client
res.setHeader('contextheader', JSON.stringify(context) || '');
// Send response
return data;
}
In our actual service we send the headers to our EpicorRestNode
module in the method signature.
import { BpmData, CallContext, Client, Context } from 'epicor-rest-node/dist/models/CallContext';
public async callEFX(user: any, library: string, functionName: string, params: any, callContext: string | undefined = undefined): Promise<any> {
let efxData = undefined;
let respCallContext = undefined
let session = await EpicorRest.Createsession().then((success) => {
// Convert the string passed in to an object of CallContext
const reqCallContext = new CallContext(JSON.parse(callContext).Context);
// Modify the call context as you need
reqCallContext.Context.BpmData[0].Character01 = "MyCustomAppSentThis";
// Call our method in the EpicorRestNode module passing CallContext as a param
efxData = await this.EfxPost(library, functionName, params, false, reqCallContext).then((res) => {
respCallContext = new CallContext(JSON.parse(res.headers['contextheader']).Context); // Capture the callcontext from response headers
return res.data;
}).catch(err => {
console.log(err);
}).finally(() => {
EpicorRest.DestroySession();
});
}).catch((ex) => {
console.log(ex);
});
// Here we return our response data and the call context as two seperate objects to our controller above
return { data: efxData, context: respCallContext };
}
Additionally if you wanted to you could create an interface for that return data so in your controller you can get the type cast autocomplete goodness
export interface EpicorResponse {
data: any;
context: CallContext;
}
// In our service we switch our promise from 'any'
public async callEFX(user: any, library: string, functionName: string, params: any, callContext: string | undefined = undefined): Promise<any> {
// to 'EpicorResponse'
public async callEFX(user: any, library: string, functionName: string, params: any, callContext: string | undefined = undefined): Promise<EpicorResponse> {