Custom API for updating products

System Information
  • V4:
  • macOS:
  • Postgres:

Hello Strapi Community!

I’m currently setting up V4 to update products collection with a PUT request after receiving another request from my PIM.

I have just generated a custom API using strapi generate CLI and basically set up two functions to do the above mentioned task within my controller for update-products route.

"use strict";

var axios = require("axios");
var qs = require("qs");

function getUpdates() {
  var baseLinkerData = qs.stringify({
    method: "getInventoryProductsList",
    parameters: '{"inventory_id": 3807}',
  });
  var config = {
    method: "post",
    url: "https://api.baselinker.com/connector.php",
    headers: {
      "X-BLToken": `${process.env.BL_TOKEN}`,
      "Content-Type": "application/x-www-form-urlencoded",
    },
    data: baseLinkerData,
  };

  axios(config)
    .then(function (response) {
      // const updates = JSON.stringify(response.data.products);
      const updates = response.data.products;
      // console.log(updates);
      updateProducts(updates);
    })
    .catch(function (error) {
      console.log(error);
    });
}

function updateProducts(productList) {
  // Prepare a request

  for (const [key, value] of Object.entries(productList)) {

    // Mapping of the product data
    var data = JSON.stringify({
      data: {
        id: value.id,
        ean: value.ean ? value.ean : null,
        sku: value.sku ? value.sku : null,
        name: value.name,
        quantity: value.stock ? parseInt(value.stock.bl_5076) : 0,
        price_brutto: value.prices ? parseFloat(value.prices["3624"]) : 0,
      },
    });
    console.log(data);

   // Config for the request
    var config = {
      method: "put",
      url: `${process.env.MY_HEROKU_URL}/api/products/${key}`,
      headers: {
        Authorization: process.env.STRAPI_API_AUTH,
        "Content-Type": "application/json",
      },
      data: data,
    };

    // Send the request

    axios(config)
      .then(function (response) {
        console.log(JSON.stringify(response.data));
      })
      .catch(function (error) {
        console.log(error);
      });
    // });
  }
}
/**
 * A set of functions called "actions" for `update-products`
 */

module.exports = {
  exampleAction: async (ctx, next) => {
    try {
      getUpdates();
      ctx.body = "ok";
    } catch (err) {
      ctx.body = err;
    }
  },
};

With this code I am getting the 405 error:


Error: connect EMFILE 00.000.00.000:443 - Local (undefined:undefined)
    at internalConnect (net.js:934:16)
    at defaultTriggerAsyncIdScope (internal/async_hooks.js:452:18)
    at GetAddrInfoReqWrap.emitLookup [as callback] (net.js:1077:9)
    at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:73:8) {
  errno: -24,
  code: 'EMFILE',
  syscall: 'connect',
  address: '00.000.00.000',
  port: 443,
  config: {
    transitional: {
      silentJSONParsing: true,
      forcedJSONParsing: true,
      clarifyTimeoutError: false
    },
    adapter: [Function: httpAdapter],
    transformRequest: [ [Function: transformRequest] ],
    transformResponse: [ [Function: transformResponse] ],
    timeout: 0,
    xsrfCookieName: 'XSRF-TOKEN',
    xsrfHeaderName: 'X-XSRF-TOKEN',
    maxContentLength: -1,
    maxBodyLength: -1,
    validateStatus: [Function: validateStatus],
    headers: {
      Accept: 'application/json, text/plain, */*',
      'Content-Type': 'application/json',
      Authorization: 'process.env.BEARER',
      'User-Agent': 'axios/0.24.0',
      'Content-Length': 82
    },
    method: 'put',
    url: 'https://sampleurl.herokuapp.com/api/products/16611',
    data: '{"data":{"id":21847637,"ean":null,"sku":null,"name":"GRA O TRON Winter is here – Kubek termiczny","quantity":1,"price_brutto":55.99}}'
    },
  request: <ref *1> Writable {
    _writableState: WritableState {
      objectMode: false,
      highWaterMark: 16384,
      finalCalled: false,
      needDrain: false,
      ending: false,
      ended: false,
      finished: false,
      destroyed: false,
      decodeStrings: true,
      defaultEncoding: 'utf8',
      length: 0,
      writing: false,
      corked: 0,
      sync: true,
      bufferProcessing: false,
      onwrite: [Function: bound onwrite],
      writecb: null,
      writelen: 0,
      afterWriteTickInfo: null,
      buffered: [],
      bufferedIndex: 0,
      allBuffers: true,
      allNoop: true,
      pendingcb: 0,
      prefinished: false,
      errorEmitted: false,
      emitClose: true,
      autoDestroy: true,
      errored: null,
      closed: false
    },
    _events: [Object: null prototype] {
      response: [Function: handleResponse],
      error: [Function: handleRequestError]
    },
    _eventsCount: 2,
    _maxListeners: undefined,
    _options: {
      maxRedirects: 21,
      maxBodyLength: 10485760,
      protocol: 'https:',
      path: '/api/products/16611',
      method: 'PUT',
      headers: [Object],
      agent: undefined,
      agents: [Object],
      auth: undefined,
      hostname: 'sampleurl.herokuapp.com',
      port: null,
      nativeProtocols: [Object],
      pathname: '/api/products/16611'
    },
    _ended: false,
    _ending: true,
    _redirectCount: 0,
    _redirects: [],
    _requestBodyLength: 82,
    _requestBodyBuffers: [ [Object] ],
    _onNativeResponse: [Function (anonymous)],
    _currentRequest: ClientRequest {
      _events: [Object: null prototype],
      _eventsCount: 7,
      _maxListeners: undefined,
      outputData: [],
      outputSize: 0,
      writable: true,
      destroyed: false,
      _last: true,
      chunkedEncoding: false,
      shouldKeepAlive: false,
      _defaultKeepAlive: true,
      useChunkedEncodingByDefault: true,
      sendDate: false,
      _removedConnection: false,
      _removedContLen: false,
      _removedTE: false,
      _contentLength: null,
      _hasBody: true,
      _trailer: '',
      finished: false,
      _headerSent: true,
      socket: [TLSSocket],
      _header: 'PUT /api/products/16611 HTTP/1.1\r\n' +
        'Accept: application/json, text/plain, */*\r\n' +
        'Content-Type: application/json\r\n' +
        'Author^Cse,
      parser: null,
      maxHeadersCount: null,
      reusedSocket: false,
      host: 'sampleurl.herokuapp.com',
      protocol: 'https:',
      _redirectable: [Circular *1],
      [Symbol(kCapture)]: false,
      [Symbol(kNeedDrain)]: false,
      [Symbol(corked)]: 0,
      [Symbol(kOutHeaders)]: [Object: null prototype]
    },
    _currentUrl: 'https://sampleurl.herokuapp.com/api/products/16611',
    [Symbol(kCapture)]: false
  },
  response: undefined,
  isAxiosError: true,
  toJSON: [Function: toJSON]
}

I just can’t wrap my head around this, also I was wondering what would be another way to update the collection without using API when the data is already pulled with the first request?

Thanks!

Right now I’m thinking of using Query Engine API to populate the collection with the incoming data. Would that be a better approach?

If I understand correctly what you are trying to do - internal REST call is odd.
I also import products from external source - but I do it by using these two calls - depending on my control hash value and if product exists or not.

await strapi.db.query('api::product.product').update(query)
await strapi.db.query('api::product.product').create(query)

query for update and create are the same - but the create one does not have “where” part

const query = {
  where: {
    id: existing_products[new_data.import_code].id
  },
  data: {
    ...new_data,
    import_date_time: new Date().toISOString(),
    import_hash: hash
  }
}

Also - notice the hash value - I calculate it from “new_data” and import only if it different - so you do not have to update the record if nothing changed.

Hope that helps.

1 Like

Yes, the internal REST call was my first approach since I didn’t even get to the Query Engine API point in Strapi documentation. Also I am a complete beginner with backend programming so pardon my bizarre ideas :sweat_smile: .

The solution that you presented is exactly what I was looking for! Thank you very much and I hope you have a great day cause you just have made mine! :pray:

1 Like

hey how did you update the product endpoint when it only takes id as a parameter

Please notice the
„…new_data” spread operator with an object containing all the data to be updated by the API