Creating the node.js client application
Less than to read
In this section, you will build a simple application using Node.js which will perform GraphQL queries on Sage X3.
Initializing the application
Open a command prompt and enter the following commands:
mkdir x3-graphql-tuto
cd x3-graphql-tuto
npm init -y
npm install typescript @types/node rimraf --save-dev
mkdir src
Configuring TypeScript
The client application requires some additional configuration steps:
-
Generate the tsconfig.json file:
npx tsc --init --rootDir src --outDir build \ --esModuleInterop --resolveJsonModule --target es2020 --lib es2020 \ --module commonjs --allowJs true --noImplicitAny true
-
Edit the package.json file and add the following lines in the script before the “test” entry:
"start": "npm run build && node build/index.js", "build": "rimraf ./build && tsc",
Adding the required dependencies
The application requires some external dependencies to perform certain tasks.
npm install axios jsonwebtoken
npm install @types/jsonwebtoken --save-dev
The final package.json file should be like the following:
{
"name": "x3-graphql-tuto",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node build/index.js",
"build": "rimraf ./build && tsc",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/jsonwebtoken": "^8.5.1",
"@types/node": "^14.14.41",
"rimraf": "^3.0.2",
"typescript": "^4.2.4"
},
"dependencies": {
"axios": "^0.21.1",
"jsonwebtoken": "^8.5.1"
}
}
Editing the source code
index.ts
import { ConnectedAppOptions, getAuthToken } from './auth';
import { query } from './graphql';
const config = require('../config.json');
const [tokenName, endpoint, request] = process.argv.slice(2);
const usage = () => { console.log('\n\u{1F4E2} usage: npm start [token-name] [endpoint] [graphql-request]\n'); process.exit(1); }
const error = (msg:string) => console.error(`\u{274C} ${msg}`);
if (!tokenName || !request) {
error('missing argument');
usage();
}
const tokenInfo = config[tokenName] as ConnectedAppOptions | undefined;
if (!tokenInfo) {
error(`bad token name: ${tokenName}, check your config.json file!`);
usage();
}
(async () => {
try {
const token = getAuthToken(tokenInfo!);
const result = await query(tokenInfo!.url, endpoint, token, request);
console.log(JSON.stringify(result.data, null, 2));
} catch(e){
error(e.message);
}
})();
auth.ts
import { sign, } from 'jsonwebtoken';
interface JwtTokenInfo {
iss: string;
sub?: string;
aud?: string;
iat: number;
exp: number;
};
export interface ConnectedAppOptions{
clientId: string;
secret: string;
url: string;
user:string;
lifetime: number;
}
export function getAuthToken(options:ConnectedAppOptions, audience?:string): string {
const {clientId,secret,user,lifetime} = options;
// If the token is issued in the future, the server will return an error 21
// In some cases, the time difference between computer might cause this issue,
// so it may require to adjust the issued at (iat) value.
const nowInSeconds = Math.floor(Date.now() / 1000) - 30;
const expInSeconds = nowInSeconds + (lifetime || 300);
const token:JwtTokenInfo = {
iss: clientId,
aud: audience || '',
iat: nowInSeconds,
exp: expInSeconds,
};
if (user) token.sub = user;
return sign(token, secret);
}
graphql.ts
import * as https from 'https';
const axios = require('axios').create({
httpsAgent: new https.Agent({
// set this only in dev environment to work with self-signed certificates
rejectUnauthorized: false,
}),
});
export async function query(url:string, endpoint:string, token:string, request:string) : Promise<any> {
return await axios.post(url, {
query: request
},{
headers: {
authorization: `Bearer ${token}`,
'x-xtrem-endpoint': endpoint,
},
});
}
Adding a configuration file for the client application
This test application uses parameters stored in a config.json file to generate the token.
-
Create the config.json file at the root of the application with the following template. Replace the values with the ones created during the previous steps.
{ "adc":{ "clientId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "secret": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy", "user": "adc", "lifetime":600, "url": "http://myx2Server:8124/xtrem/api" } }
-
Build and run the application. In the following example, REPOSX3_REPOSX3 is the name of the connection endpoint. You can find its name in the Endpoints function (Administration > Administration > Endpoints > Endpoints).
npm run build npm run start adc REPOSX3_REPOSX3 "{xtremX3Structure{country{query{edges{node{code}}}}}}"
-
A response similar to the following example displays.
{
"data": {
"xtremX3Structure": {
"country": {
"query": {
"edges": [
{
"node": {
"code": "AD"
}
},
{
"node": {
"code": "AE"
}
},
{
"node": {
"code": "AF"
}
},
{
"node": {
"code": "AL"
}
},
{
"node": {
"code": "AM"
}
},
{
"node": {
"code": "AN"
}
},
{
"node": {
"code": "AO"
}
},
{
"node": {
"code": "AR"
}
},
{
"node": {
"code": "AT"
}
},
{
"node": {
"code": "AU"
}
},
{
"node": {
"code": "AW"
}
},
{
"node": {
"code": "AX"
}
},
{
"node": {
"code": "AZ"
}
},
{
"node": {
"code": "BA"
}
},
{
"node": {
"code": "BB"
}
},
{
"node": {
"code": "BD"
}
},
{
"node": {
"code": "BE"
}
},
{
"node": {
"code": "BF"
}
},
{
"node": {
"code": "BG"
}
},
{
"node": {
"code": "BH"
}
}
]
}
}
}
},
"extensions": {
"diagnoses": []
}
}