{"id":1143,"date":"2018-11-28T15:26:22","date_gmt":"2018-11-28T14:26:22","guid":{"rendered":"https:\/\/zaven.co\/blog\/?p=1143"},"modified":"2025-04-08T19:55:07","modified_gmt":"2025-04-08T17:55:07","slug":"azure-cosmosdb-js-funcions-with-gitlab-ci","status":"publish","type":"post","link":"https:\/\/zaven.co\/blog\/azure-cosmosdb-js-funcions-with-gitlab-ci\/","title":{"rendered":"Effortless deployment of Azure CosmosDB JS functions with GitLab CI"},"content":{"rendered":"\n<p class=\"lead\">Working with CosmosDB JavaScript triggers and stored procedures can be problematic, especially when your application consists of three or four separate development environments. The solution we need is found in the effortless deployment of Azure CosmosDB JS functions with GitLab CI.<\/p>\n\n\n\n<!--more-->\n\n\n\n<h2 class=\"p1 wp-block-heading\"><span class=\"s1\">The city of <\/span><span class=\"s2\">CosmosDB<\/span><span class=\"s1\"> is riddled with crime<\/span><\/h2>\n\n\n\n<p class=\"p1\"><span class=\"s1\">Working with <\/span><em><span class=\"s2\">CosmosDB JavaScript triggers <\/span><\/em><span class=\"s1\">and <\/span><span class=\"s3\">stored procedures <\/span><span class=\"s1\">can be problematic, especially when your application consists of three or four separate development environments. After you\u2019ve created your function in Azure&#8217;s fun built-in notepad, you have to copy and paste the code to each collection in every environment by hand.<\/span><\/p>\n\n\n\n<p class=\"p1\"><span class=\"s1\">It&#8217;s not difficult to imagine the amount of work one has to put in syncing this code across every tier, not to mention that this approach is prone to errors and often results in inconsistencies; it is quite easy to forget to propagate the changes to each environment.<\/span><\/p>\n\n\n\n<p class=\"p1\"><span class=\"s1\">Sometimes your application is branded for different clients. 2 apps in 4 environments with separate databases, each with 3 triggers, 3 stored procedures; you end up copying and pasting the functions roughly 42 times, not to mention logging into different accounts in between. Yikes!<\/span><\/p>\n\n\n\n<p class=\"p2\"><span class=\"s1\">Furthermore, we don&#8217;t want functions to just lie around in the <\/span><em><span class=\"s2\">Azure Portal<\/span><\/em><span class=\"s1\">. Ideally, we would want to keep them in an external repository, safe from frequent collection deletions and migrations, particularly in the early stages of development.<\/span><\/p>\n\n\n\n<h3 class=\"p1 wp-block-heading\"><span class=\"s1\">The hero we need and also deserve<\/span><\/h3>\n\n\n\n<p class=\"p2\"><span class=\"s1\">The solution for all this madness is putting to work the <\/span><em><span class=\"s2\">GitLab CI<\/span><\/em><span class=\"s2\">! With a bit of help from NodeJS.<\/span><\/p>\n\n\n\n<p class=\"p3\"><span class=\"s1\">With <abbr title=\"Continous Integration\">CI<\/abbr> configured for our task, we&#8217;re not only able to effortlessly deploy the code to all environments via the <\/span><em><span class=\"s3\">CosmosDB<\/span><\/em><span class=\"s1\"> REST API, but we can also use Babel.js to \u201cminify\u201d the code and make it faster as a result (which matters greatly at scale).<\/span><\/p>\n\n\n\n<h2 class=\"p1 wp-block-heading\"><span class=\"s1\">Azure<\/span> <span class=\"s1\">CosmosDB <\/span><span class=\"s3\">JS functions: the beginning<\/span><\/h2>\n\n\n\n<h3 class=\"p2 wp-block-heading\"><span class=\"s3\">The project structure<\/span><\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">cosmosdb-functions\n  \u2502\n  \u251c\u2500\u2500 dist &lt;- Babel destination directory\n  \u2502\n  \u251c\u2500\u2500 node_modules\n  \u2502\n  \u251c\u2500\u2500 src\n  \u2502    \u251c\u2500\u2500 procedures\n  \u2502    \u2514\u2500\u2500 triggers\n  \u2502\n  .gitignore\n  .gitlab-ci.yml\n  package.json\n  package-lock.json\n  synchronizeWithCosmosDB.js<\/code><\/pre>\n\n\n\n<h3 class=\"p1 wp-block-heading\">Package.json and its dependencies<\/h3>\n\n\n\n<p class=\"p2\">First things first, <b>let&#8217;s take care of Package.json and its dependencies<\/b>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"json\" class=\"language-json\">{\n     \"name\": \"cosmosdb-functions\",\n     \"version\": \"1.0.0\",\n     \"description\": \"Sample CosmosDB triggers and stored procedures\",\n     \"scripts\": {\n       \"start\": \"babel src --out-dir dist\"\n     },\n     \"babel\": {\n       \"presets\": [\n         [\n           \"minify\",\n           {\n             \"removeConsole\": true\n           }\n         ]\n       ],\n       \"plugins\": [\n         \"babel-plugin-loop-optimizer\"\n       ]\n     },\n     \"devDependencies\": {\n       \"@babel\/cli\": \"^7.1.2\",\n       \"@babel\/core\": \"^7.1.2\",\n       \"babel-preset-minify\": \"^0.5.0\",\n       \"babel-plugin-loop-optimizer\": \"^1.4.1\",\n       \"crypto\": \"^1.0.1\"\n     },\n     \"repository\": {\n       \"type\": \"git\",\n       \"url\": \"ssh:\/\/git@git.your-git-repo.com:1337\/sample-repo\/cosmosdb-functions.git\"\n     }\n}\n<\/code><\/pre>\n\n\n\n<p>Let\u2019s look at <code>devDependencies<\/code>:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>For Babel to work, first we have to add its essence; that is the Babel-CLI &amp; Babel-Core modules. After that, we will add Babel tools, which will perform the actual code transformation.<\/li><li><code>babel-preset-minify<\/code> will \u201cminify\u201d or &#8220;uglify&#8221; our code, so the output file will be smaller in size, and babel-plugin-loop-optimizer will transform any <code>map()<\/code> or <code>forEach()<\/code> to normal for loops, also reducing execution times.<\/li><li>Last but not least, we will add crypto, which will be needed later on.<\/li><\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Babel configuration<\/h3>\n\n\n\n<p>The <code>babel<\/code> property in <code>package.json<\/code> contains all the info regarding how Babel should process your source files. There is only one preset declared: <code>minify<\/code>, but with the additional <code>removeConsole<\/code> option which will remove all <code>console.log()<\/code> occurrences in the code during the minification process. Inside the plugins property we declare the loop optimizer.<\/p>\n\n\n\n<p>What we configured above is ready to be executed with one simple command:<br><code>babel src --out-dir dist<\/code>. With this command we are telling Babel to get everything from the <code>src<\/code> directory, and put the effects of the transformation into <code>dist<\/code> directory. Easy!<\/p>\n\n\n\n<p>This command has to be put inside the <code>scripts<\/code> property. Later we\u2019ll ask the CI runner to use it.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The warm-up<\/h3>\n\n\n\n<p>We start by importing all the required modules to our <code>synchronizeWithCosmosDB.js<\/code> file and then we implement a function for serving all the http requests, using vanilla NodeJS https module.<\/p>\n\n\n\n<pre class=\"wp-block-code language-javascript line-numbers\"><code lang=\"javascript\" class=\"language-javascript\">const crypto = require('crypto');\nconst fileSystem = require('fs');\nconst https = require('https');\n\nasync function makeRequest(requestOptions, requestBody) {\n  return new Promise((resolve, reject) =&gt; {\n    const request = https.request(requestOptions, (response) =&gt; {\n      const chunks = [];\n      response.on('data', data =&gt; {\n        chunks.push(data);\n      });\n      response.on('end', () =&gt; {\n        let responseBody = Buffer.concat(chunks);\n        responseBody = JSON.parse(responseBody);\n        resolve(responseBody);\n      });\n    });\n    request.on('error', (error) =&gt; {\n      reject(error);\n    });\n    request.write(JSON.stringify(requestBody));\n    request.end();\n  });\n}<\/code><\/pre>\n\n\n\n<p>The <code>https<\/code> node module can be a little unintuitive to use, but you don&#8217;t really need to know how the <code>makeRequest()<\/code> function works, only that it takes in two parameters:<br>a request options:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">const procedurePutOptions = {\n  hostname: HOSTNAME,\n  path: `\/${COLLECTION_RESOURCE_ID}\/sprocs\/${requestBody.id}`,\n  method: 'PUT',\n  headers: {\n    'x-ms-version': COSMOSDB_API_VERSION,\n    'Content-Type': 'application\/json',\n    'Authorization': authTokenPUT,\n    'x-ms-date': DATE\n  }\n};<\/code><\/pre>\n\n\n\n<p>and the request body:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">const requestBody = {\n  body: procedure,  \/\/ the code processed by babel goes in here\n  id: procedureFileName\n};<\/code><\/pre>\n\n\n\n<p>Now that we\u2019re done with the easy part, <strong>it&#8217;s time for some Microsoft wisdom.<\/strong><\/p>\n\n\n\n<div class=\"wp-block-image wp-image-1148 size-full\"><figure class=\"aligncenter\"><img loading=\"lazy\" decoding=\"async\"   src=\"https:\/\/zaven.co\/blog\/wp-content\/uploads\/2018\/11\/image4.gif\" alt=\"Azure CosmosDB\" class=\"wp-image-1148\"\/><figcaption>Working with Microsoft documentation, source: https:\/\/agileleanlife.com<\/figcaption><\/figure><\/div>\n\n\n\n<h3 class=\"p1 wp-block-heading\"><span class=\"s1\">The main villain<\/span><\/h3>\n\n\n\n<p class=\"p2\"><span class=\"s1\">You\u2019ve probably noticed that weirdly named <code>authTokenPUT<\/code> sitting in headers. For some reason, the <em>CosmosDB<\/em> REST API requires a new token for every method and for each single resource you&#8217;re trying to modify.<\/span><\/p>\n\n\n\n<p class=\"p2\"><span class=\"s1\">But fear thou not, for I am with thee. I have been through this shadowy valley, so you don&#8217;t have to do it yourself.<\/span><\/p>\n\n\n\n<p class=\"p3\"><span class=\"s1\"><b>The result of this journey is a token generation function<\/b> that looks like this:<\/span><\/p>\n\n\n\n<pre class=\"wp-block-code language-javascript line-numbers\"><code lang=\"javascript\" class=\"language-javascript\">function getAuthorizationToken(httpMethod, resourcePath, resourceType, date, databasePrimaryKey) {\n  const params = `${httpMethod.toLowerCase()}\\n${resourceType.toLowerCase()}\\n${resourcePath}\\n${date.toLowerCase()}\\n\\n`;\n\n  const key = Buffer.from(databasePrimaryKey, 'base64');\n  const body = Buffer.from(params, 'utf8');\n  const signature = crypto.createHmac('sha256', key).update(body).digest('base64');\n\n  return encodeURIComponent(`type=master&amp;ver=1.0&amp;sig=${signature}`);\n}<\/code><\/pre>\n\n\n\n<p>It&#8217;s based on their sample function. <strong>Do not modify any of this.<\/strong> It has to have exactly two <code>\\n<\/code> at the end of the <code>params<\/code> string.<br>How does it work? We don&#8217;t need to know that! All that we care about is how to use it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">const authTokenPUT = getAuthorizationToken(\n  'PUT',\n  `${COLLECTION_RESOURCE_ID}\/sprocs\/${requestBody.id}`,\n  'sprocs',\n  DATE,\n  DATABASE_PRIMARY_KEY\n);<\/code><\/pre>\n\n\n\n<p>In the first param we specify the http method we want to use with this particular request.<\/p>\n\n\n\n<p>The second param wasn\u2019t so easy to figure out. After some time, the final thing that worked is a path combined of these elements:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><code>COLLECTION_RESOURCE_ID<\/code>,the path to your collection in following format: <code>dbs\/{database}\/colls\/{collectionName}<\/code> (e.g. <code>dbs\/exampleDB\/colls\/sample_collection<\/code>)<\/li><li><code>\/sprocs\/<\/code>, which means stored procedures directory associated with sample_collection<\/li><li>The tzn<code>id<\/code> (the name) of the stored procedure we want to update.<\/li><\/ul>\n\n\n\n<p>Next, we have another <code>\u2019sprocs\u2019<\/code>, which is needed, for some reason. It&#8217;s the <code>resourceType<\/code>, and in our case, we will be using two of them: <code>sprocs<\/code> and <code>triggers<\/code>.<\/p>\n\n\n\n<p>The fourth param, <code>DATE<\/code> is the date we generate once as a constant, for all our requests to use. Please notice, that the date has to be exactly the same as the <code>x-ms-date<\/code> in the headers. This fact was also omitted in the docs.<\/p>\n\n\n\n<p>The last parameter is your database\u2019s primary key which is confusingly referred to in the docs as \u201cmaster key\u201d.<\/p>\n\n\n\n<p>The token that we get in return is valid for 15 minutes for this particular resource, plenty of time to do our business.<br><strong><br>Now that we have everything we need, let&#8217;s try to send our stored procedure to CosmosDB.<\/strong><\/p>\n\n\n\n<h2 class=\"p1 wp-block-heading\"><span class=\"s1\">Azure functions CosmosDB<\/span><span class=\"s2\">: f<\/span>irst call<\/h2>\n\n\n\n<p>The <code>synchronizeWithCosmosDB.js<\/code> now looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">DATABASE_PRIMARY_KEY = 'databaseprimarykeythatendswithtwoequalsigns==';\nCOLLECTION_RESOURCE_ID = 'dbs\/exampleDB\/colls\/sample_collection';\nHOSTNAME = 'sample-account-1.documents.azure.com';\n\nconst COSMOSDB_API_VERSION = '2017-02-22'; \/\/ https:\/\/docs.microsoft.com\/en-us\/rest\/api\/cosmos-db\/ &lt;-latest versions here\nconst PROCEDURES_PATH = '.\/dist\/procedures';\nconst TRIGGERS_PATH = '.\/dist\/triggers';\n\nconst DATE = new Date().toUTCString();\n\ntry {\n\n  tryPutProcedure('TestProcedure.js').then(console.log);\n\n} catch (error) {\n  console.log(error.message);\n}\n\nasync function tryPutProcedure(procedureFileName) {\n  const procedure = fileSystem.readFileSync(`${PROCEDURES_PATH}\/${procedureFileName}`, 'utf8');\n  const requestBody = {\n    body: procedure,\n    id: procedureFileName.replace('.js', '')\n  };\n\n  const authTokenPUT = getAuthorizationToken(\n    'PUT',\n    `${COLLECTION_RESOURCE_ID}\/sprocs\/${requestBody.id}`,\n    'sprocs',\n    DATE,\n    DATABASE_PRIMARY_KEY\n  );\n\n  const procedurePutOptions = {\n    hostname: HOSTNAME,\n    path: `\/${COLLECTION_RESOURCE_ID}\/sprocs\/${requestBody.id}`,\n    method: 'PUT',\n    headers: {\n      'x-ms-version': COSMOSDB_API_VERSION,\n      'Content-Type': 'application\/json',\n      'Authorization': authTokenPUT,\n      'x-ms-date': DATE\n    }\n  };\n\n  let response = await makeRequest(procedurePutOptions, requestBody);\n\n  return response;\n}\n\n\/\/ the makeRequest and getAuthorizationToken functions are somewhere below<\/code><\/pre>\n\n\n\n<p>We are reading the minified file synchronously with the help of the nodeJS file system. It is important to set the encoding to &#8216;utf-8&#8217;.<\/p>\n\n\n\n<p>In the requestBody, the parameter <code>id<\/code> receives the name of the file minus the file extension &#8211; for aesthetics.<\/p>\n\n\n\n<p>In this snippet, I have provided the name of the procedure in <code>tryPutProcedure()<\/code> manually. We\u2019ll take care of this later.<\/p>\n\n\n\n<p>Let&#8217;s head to the terminal and say <code>npm start<\/code>, and then <code>node synchronizeWithCosmosDB<\/code>.<br>If everything goes right, we should see a result similar to this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">{ body: 'async function testProcedure(a){try{}catch(a){}}',\n    id: 'TestProcedure',\n    _rid: 'dr4DAMsTT78EAAAAAAAAgA==',\n    _self: 'dbs\/dr4DAA==\/colls\/dr4DAMsTT78=\/sprocs\/dr4DAMsTT78EAAAAAAAAgA==\/',\n    _etag: '\"00007fd4-0000-0000-0000-5bcf327d0000\"',\n    _ts: 1540305533 }<\/code><\/pre>\n\n\n\n<p>But there is a problem with that and there is a reason why we have named the function <code>tryPutProcedure()<\/code>. When we make a PUT request, we are assuming that the procedure code is already in CosmosDB, and most times this assumption will prove to be true. But sometimes we\u2019ll get a response that looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">{ code: 'NotFound',\n  message:\n   'Message: {\"Errors\":[\"Resource Not Found\"]}\\r\\nActivityId: 756b133f-9b2c-40a1-8f26-ea3f9847719a, Request URI: \/apps\/f3d2423f-cec0-4d0f-ba12-5146d90faaa7\/services\/5d583876-e9c2-4794-be01-a5aa1169e7b0\/partitions\/d78876e1-7ad6-4b49\n-aa73-9e1711672df1\/replicas\/131841768030204810p\/, RequestStats: \\r\\nRequestStartTime: 2018-10-24T09:51:49.5582642Z, Number of regions attempted: 1\\r\\n, SDK: Microsoft.Azure.Documents.Common\/2.0.0.0' }<\/code><\/pre>\n\n\n\n<p>Let\u2019s fix that, by adding a condition right after our <code>makeRequest()<\/code> call:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">\/\/...more code...\/\/\n    }\n  };\n\n  let response = await makeRequest(procedurePutOptions, requestBody);\n\n  if (response.code === 'NotFound') {\n    response = await postProcedure(requestBody);\n  }\n\n  return response;\n}<\/code><\/pre>\n\n\n\n<p>This time, when we get a response with <code>NotFound<\/code> status, we\u2019ll be ready. The implementation of <code>postProcedure()<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">async function postProcedure(requestBody) {\n  const authTokenPOST = getAuthorizationToken(\n    'POST',\n    COLLECTION_RESOURCE_ID,\n    'sprocs',\n    DATE,\n    DATABASE_PRIMARY_KEY\n  );\n\n  const procedurePostOptions = {\n    hostname: HOSTNAME,\n    path: `\/${COLLECTION_RESOURCE_ID}\/sprocs`,\n    method: 'POST',\n    headers: {\n      'x-ms-version': COSMOSDB_API_VERSION,\n      'Content-Type': 'application\/json',\n      'Authorization': authTokenPOST,\n      'x-ms-date': DATE\n    }\n  };\n\n  return makeRequest(procedurePostOptions, requestBody);\n}<\/code><\/pre>\n\n\n\n<p>Take a look at the token generation. Now we\u2019re providing only the path to the collection, and the resource type, because now all we are doing is create a new resource in <code>\/sprocs<\/code>. Of course, all of this could be done differently; we could get a <a href=\"https:\/\/docs.microsoft.com\/en-us\/rest\/api\/cosmos-db\/list-stored-procedures\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">list of stored procedures<\/a> belonging to our collection and then compare the local and remote data and send only the data that needs to be sent. But we don\u2019t want to use a sledgehammer to crack a nut, right?<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Map all the things!<\/h3>\n\n\n\n<p>All we need to do now, is to take all the files in <code>.dist\/procedures<\/code> and send them to the API:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">async function synchronizeProcedures() {\n  const allProcedures = fileSystem.readdirSync(PROCEDURES_PATH);\n  return Promise.all(allProcedures.map(tryPutProcedure));\n}<\/code><\/pre>\n\n\n\n<p>Piece of cake!<\/p>\n\n\n\n<p>In the try-catch we used before, we can say:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">synchronizeProcedures()\n    .then(console.log)\n    .catch(error =&gt; {\n        ci_job_fail_with_error(error); \/\/does not matter for now\n      }\n    );<\/code><\/pre>\n\n\n\n<p class=\"p1\"><span class=\"s1\"><b>Done! Now all your <\/b><\/span><span class=\"s2\"><b>stored procedures<\/b><\/span><span class=\"s1\"><b> are chillin\u2019 at <\/b><\/span><span class=\"s2\"><b>CosmosDB\u2019s<\/b><\/span><span class=\"s1\"><b> crib.<\/b><\/span><\/p>\n\n\n\n<h3 class=\"p1 wp-block-heading\"><span class=\"s1\">The triggers<\/span><\/h3>\n\n\n\n<p class=\"p3\"><span class=\"s2\">Trigger functions<\/span><span class=\"s1\"> are a bit harder to upload, but that\u2019s nothing we can\u2019t deal with.<\/span><\/p>\n\n\n\n<p class=\"p3\"><span class=\"s1\">As you know, to define a trigger, we need to specify additional info about its behavior. <\/span><\/p>\n\n\n\n<p class=\"p3\"><span class=\"s1\">First, we tell it when it should fire, i.e. the <code>Trigger Type<\/code>(Pre, Post), and second, what events it should act upon &#8211; the <code>Trigger Operation<\/code> (All, Create, Replace, Delete). <\/span><\/p>\n\n\n\n<p class=\"p3\"><span class=\"s1\">Where do we keep that information?<\/span><\/p>\n\n\n\n<p class=\"p3\"><span class=\"s1\">In the file name. <\/span><\/p>\n\n\n\n<p class=\"p5\"><span class=\"s1\">And hence the naming convention of trigger files shall be:<\/span><\/p>\n\n\n\n<p><code>TriggerName-TriggerType-TriggerOperation.<\/code><br>Example: <code>TheBestTrigger-Post-Delete.js<\/code><\/p>\n\n\n\n<p>Now, we will construct the payload of the trigger like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">async function tryPutTrigger(triggerFileName) {\n  const trigger = fileSystem.readFileSync(`${TRIGGERS_PATH}\/${triggerFileName}`, 'utf8');\n\n  const triggerInfo = triggerFileName.split('-');\n\n  const requestBody = {\n    body: trigger,\n    id: triggerInfo[0],\n    triggerType: triggerInfo[1],\n    triggerOperation: triggerInfo[2].replace('.js', '')\n  };\n\n\n\/\/...the remaining code...<\/code><\/pre>\n\n\n\n<p>The rest of the code is essentially the same, except that during the token generation we need to replace the <code>'sprocs'<\/code> in path and the resource type with <code>'triggers'<\/code>. The same applies to the construction of the request options.<\/p>\n\n\n\n<h2 class=\"p1 wp-block-heading\"><span class=\"s1\">GitLab CI setup for CosmosDB<\/span><\/h2>\n\n\n\n<h3 class=\"p1 wp-block-heading\"><span class=\"s1\">GitLab CI variables enter the scene<\/span><\/h3>\n\n\n\n<p class=\"p3\"><span class=\"s3\">Eventually, we will want to execute the syncing program with the intent to update all the code in every development environment on the <\/span><em><span class=\"s1\">GitLab CI<\/span><\/em><span class=\"s1\"> runner.<\/span><\/p>\n\n\n\n<p class=\"p3\"><span class=\"s3\">To make that happen, we have to keep the keys and paths to each of them somewhere, and that place is the <\/span><em><span class=\"s1\">GitLab CI Variables<\/span><\/em><span class=\"s1\">.<\/span><\/p>\n\n\n\n<div class=\"wp-block-image wp-image-1169 size-full\"><figure class=\"aligncenter\"><img loading=\"lazy\" decoding=\"async\"   src=\"https:\/\/zaven.co\/blog\/wp-content\/uploads\/2018\/11\/Zrzut-ekranu-2018-11-27-o-15.51.42.png\" alt=\"\" class=\"wp-image-1169\" srcset=\"https:\/\/zaven.co\/blog\/wp-content\/uploads\/2018\/11\/Zrzut-ekranu-2018-11-27-o-15.51.42.png 933w, https:\/\/zaven.co\/blog\/wp-content\/uploads\/2018\/11\/Zrzut-ekranu-2018-11-27-o-15.51.42-730x239.png 730w\" sizes=\"auto, (max-width: 933px) 100vw, 933px\" \/><figcaption>Sample CI variables for two separate environments<\/figcaption><\/figure><\/div>\n\n\n\n<p>The plan here is simple, we take for instance the <code>DATABASE_PRIMARY_KEY<\/code> and we add the environment prefix &#8211; <code>DEV<\/code>, <code>STG<\/code>, <code>PROD<\/code> etc. so it becomes <code>DEV_DATABASE_PRIMARY_KEY<\/code>.<\/p>\n\n\n\n<p>The runner we will be using to execute the program has access to these variables, and the way we tell our code to get them is easy:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">const DATABASE_PRIMARY_KEY = process.env[`DEV_DATABASE_PRIMARY_KEY`];<\/code><\/pre>\n\n\n\n<p>We also want to be able to specify the environment to which we wish to deploy the code<br>from outside of the syncing program in following fashion:<br><code>node synchronizeWithCosmosDB DEV<\/code>.<\/p>\n\n\n\n<p>Here\u2019s how to do it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">try {\n  ENVIRONMENT = process.argv[2];\n  DATABASE_PRIMARY_KEY =\nprocess.env[`${ENVIRONMENT}_DATABASE_PRIMARY_KEY`];\n  COLLECTION_RESOURCE_ID = process.env[`${ENVIRONMENT}_COLLECTION_RESOURCE_ID`];\n  HOSTNAME = process.env[`${ENVIRONMENT}_HOSTNAME`];\n\n  synchronizeProcedures()\n    .then(console.log)\n    .catch(error =&gt; {\n        ci_job_fail_with_error(error);\n      }\n    );\n\n  synchronizeTriggers()\n    .then(console.log)\n    .catch(error =&gt; {\n        ci_job_fail_with_error(error);\n      }\n    );\n\n} catch (error) {\n  console.log(`\n    Usage example:\n    node synchronizeWithCosmosDB DEV\n  `);\n  ci_job_fail_with_error(error);\n}\n\nfunction ci_job_fail_with_error(error) {\n  console.log(error.message);\n  process.exit(1);\n}<\/code><\/pre>\n\n\n\n<p>The essence is in <code>ENVIRONMENT = process.argv[2]<\/code>. It takes the last parameter after the command and assigns it to the constant, so we can utilize it in constructing the environment string.<br>Now, we can use our program with <code>node synchronizeWithCosmosDB DEV<\/code> and that enables us to take the final step.<\/p>\n\n\n\n<p>Note:<br><code>ci_job_fail_with_error()<\/code> ensures that our CI job will fail whenever something funny happens.<\/p>\n\n\n\n<div class=\"wp-block-image wp-image-1170 size-full\"><figure class=\"aligncenter\"><img loading=\"lazy\" decoding=\"async\"   src=\"https:\/\/zaven.co\/blog\/wp-content\/uploads\/2018\/11\/Zrzut-ekranu-2018-11-27-o-15.51.54.png\" alt=\"The GitLab-CI pipelines\" class=\"wp-image-1170\" srcset=\"https:\/\/zaven.co\/blog\/wp-content\/uploads\/2018\/11\/Zrzut-ekranu-2018-11-27-o-15.51.54.png 939w, https:\/\/zaven.co\/blog\/wp-content\/uploads\/2018\/11\/Zrzut-ekranu-2018-11-27-o-15.51.54-730x114.png 730w\" sizes=\"auto, (max-width: 939px) 100vw, 939px\" \/><figcaption>A failed job<\/figcaption><\/figure><\/div>\n\n\n\n<h3 class=\"wp-block-heading\">The GitLab CI pipelines<\/h3>\n\n\n\n<p>A CI pipeline is basically a group of jobs that run one after another, whenever we push the code to our GitLab repo. Of course, for that to happen, first we need to have at least one Docker CI runner available. On how to setup a CI runner, there\u2019s plenty of information around.<\/p>\n\n\n\n<p>Next, we define the <code>.gitlab-ci.yml<\/code> file containing all the instructions for the runner to execute.<\/p>\n\n\n\n<h2 class=\"p1 wp-block-heading\"><span class=\"s1\">Azure CosmosDB JS functions<\/span><span class=\"s2\">: finish the job<\/span><\/h2>\n\n\n\n<p>In our <code>.gitlab-ci.yml<\/code> file, we should define a few things before we call it a day:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">image: node:latest\n\nstages:\n  - build\n  - deploy\n\ncache:\n  paths:\n  - node_modules\/\n\nbuild:\n  stage: build\n  script:\n    - npm install\n    - npm start\n  artifacts:\n    paths:\n      - dist\/\n      - node_modules\/\n    expire_in: 15 minutes\n\ndeploy_dev:\n  stage: deploy\n  only:\n    - develop\n  script:\n    - node synchronizeWithCosmosDB DEV\n\ndeploy_stg:\n  stage: deploy\n  only:\n    - \/^release\\\/.*$\/\n  script:\n    - node synchronizeWithCosmosDB STG\n   \ndeploy_prod:\n  stage: deploy\n  only:\n    - master\n  script:\n    - node synchronizeWithCosmosDB PROD<\/code><\/pre>\n\n\n\n<p>Here we have specified two separate stages that the pipeline will want to complete on each code push &#8211; <code>build<\/code> and <code>deploy<\/code>.<br>The build stage is intended for downloading all the dependencies with the help of<br>the <code>npm install<\/code> command. After this task is finished, the job will proceed to the next one<br>&#8211; <code>npm start<\/code> where the transformation of our source files occurs.<\/p>\n\n\n\n<p>The important thing in the build stage is to keep artifacts of all the downloaded dependencies in <code>node_modules<\/code> and all the minified files in the <code>dist<\/code> directory for the <code>deploy<\/code> job, which will then use the dependencies to execute the syncing program and upload the transformed files. We do this with the help of the <code>artifacts<\/code>.<\/p>\n\n\n\n<p>The deploy stage is, first and foremost, dependent on the branch name to which the code is pushed.<\/p>\n\n\n\n<p>Any code pushed to a branch with a name that starts with <code>release\/<\/code> will inevitably end up in the staging environment.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">$ git commit -m \"That's one small pipeline for GitLabCI, one giant improvement for the development process\"\n$ git push<\/code><\/pre>\n\n\n\n<p>That\u2019s it! We\u2019re done!<\/p>\n\n\n\n<p>PS. Don\u2019t forget about a proper <code>.gitignore<\/code> file!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Effortless deployment of Azure CosmosDB JS functions &#8211; additional considerations<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">What about the CosmosDB SDK?<\/h3>\n\n\n\n<p>Yes, there are many <em>CosmosDB SDKs<\/em> available. We could, instead of writing our own syncing code just use a few things from <em>NodeJS CosmosDB SDK<\/em> and upload the functions more easily (I suppose) with their help.<\/p>\n\n\n\n<p>But of course, that is an additional dependency, and this dependency relies on other dependencies, and the more dependencies we use, the slower the pipelines. And by the way, what happened to our old-fashioned DIY culture?<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Deletions?<\/h3>\n\n\n\n<p>Our approach <strong>doesn\u2019t support deletions,<\/strong> so whenever we delete a function from our repo, that function will still be present in <em>CosmosDB.<\/em> There are multiple ways this could be solved. You are free to explore the options, improve and expand on this deployment idea for better results. If you have something interesting, please let me know in the comments below.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Alternative CI tools<\/h3>\n\n\n\n<p>But wait a minute, do I even need GitLab for this? Of course not! Any CI system will do just fine, so you can choose whichever CI you, your friends and family like.<\/p>\n\n\n\n<p>And speaking of choices, remember that you have a <a href=\"https:\/\/babeljs.io\/docs\/en\/plugins\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">whole variety of Babel plugins<\/a> available for your code optimization, which is great if your functions have to perform some heavy lifting.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Azure CosmosDB JS functions with GitLab CI: summary<\/h2>\n\n\n\n<p>If your application is branded for different clients and you\u2019d end up copying and pasting the functions roughly 42 times and logging on different accounts in between. The solution for all this madness is the effortless deployment of <em>Azure CosmosDB JS functions<\/em> with <em>GitLab CI<\/em>. More effective work equals more time to learn new things (check out our another&nbsp;<a href=\"https:\/\/zaven.co\/blog\/docker-and-spring-boot-tutorial\/\">tutorial<\/a>)!<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">You can find the source code on my <a href=\"https:\/\/github.com\/ljaniszewski\/cosmosdb-functions\" target=\"_blank\" rel=\"noopener noreferrer\">Github<\/a>.<\/h3>\n","protected":false},"excerpt":{"rendered":"<p>Working with CosmosDB JavaScript triggers and stored procedures can be problematic, especially when your application consists of three or four separate development environments. The solution we need is found in the effortless deployment of Azure CosmosDB JS functions with GitLab CI.<\/p>\n","protected":false},"author":16,"featured_media":1164,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[121,5],"tags":[114,113,123,119,118,125,122,124,115,126,116,117,8],"class_list":["post-1143","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops","category-tutorials","tag-azure","tag-azure-funcions-cosmosdb","tag-backend","tag-continuous-deployment","tag-continuous-integration","tag-datbase","tag-devops","tag-gitlab-ci","tag-javascript-cosmosdb-functions","tag-nosql","tag-stored-procedures","tag-triggers","tag-tutorial"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v24.8.1 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>CosmosDB JavaScript triggers. GitLab CI deployment to Azure | Zaven Blog<\/title>\n<meta name=\"description\" content=\"If you have severe problems with Cosmos DB JavaScript implementation, get familiar with our tips. Particularly, if you work in several environments. Check it out!\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/zaven.co\/blog\/azure-cosmosdb-js-funcions-with-gitlab-ci\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"CosmosDB JavaScript triggers. GitLab CI deployment to Azure | Zaven Blog\" \/>\n<meta property=\"og:description\" content=\"If you have severe problems with Cosmos DB JavaScript implementation, get familiar with our tips. Particularly, if you work in several environments. Check it out!\" \/>\n<meta property=\"og:url\" content=\"https:\/\/zaven.co\/blog\/azure-cosmosdb-js-funcions-with-gitlab-ci\/\" \/>\n<meta property=\"og:site_name\" content=\"Zaven Blog\" \/>\n<meta property=\"article:published_time\" content=\"2018-11-28T14:26:22+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-04-08T17:55:07+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/zaven.co\/blog\/wp-content\/uploads\/2018\/11\/Depositphotos_38768923_xl-2015-1920x1280.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1920\" \/>\n\t<meta property=\"og:image:height\" content=\"1280\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Leszek Janiszewski\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Leszek Janiszewski\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"13 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/zaven.co\/blog\/azure-cosmosdb-js-funcions-with-gitlab-ci\/\",\"url\":\"https:\/\/zaven.co\/blog\/azure-cosmosdb-js-funcions-with-gitlab-ci\/\",\"name\":\"CosmosDB JavaScript triggers. GitLab CI deployment to Azure | Zaven Blog\",\"isPartOf\":{\"@id\":\"https:\/\/zaven.co\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/zaven.co\/blog\/azure-cosmosdb-js-funcions-with-gitlab-ci\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/zaven.co\/blog\/azure-cosmosdb-js-funcions-with-gitlab-ci\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/zaven.co\/blog\/wp-content\/uploads\/2018\/11\/Depositphotos_38768923_xl-2015.jpg\",\"datePublished\":\"2018-11-28T14:26:22+00:00\",\"dateModified\":\"2025-04-08T17:55:07+00:00\",\"author\":{\"@id\":\"https:\/\/zaven.co\/blog\/#\/schema\/person\/d1d670acafec5407b963815814a1db27\"},\"description\":\"If you have severe problems with Cosmos DB JavaScript implementation, get familiar with our tips. Particularly, if you work in several environments. Check it out!\",\"breadcrumb\":{\"@id\":\"https:\/\/zaven.co\/blog\/azure-cosmosdb-js-funcions-with-gitlab-ci\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/zaven.co\/blog\/azure-cosmosdb-js-funcions-with-gitlab-ci\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/zaven.co\/blog\/azure-cosmosdb-js-funcions-with-gitlab-ci\/#primaryimage\",\"url\":\"https:\/\/zaven.co\/blog\/wp-content\/uploads\/2018\/11\/Depositphotos_38768923_xl-2015.jpg\",\"contentUrl\":\"https:\/\/zaven.co\/blog\/wp-content\/uploads\/2018\/11\/Depositphotos_38768923_xl-2015.jpg\",\"width\":5138,\"height\":3425},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/zaven.co\/blog\/azure-cosmosdb-js-funcions-with-gitlab-ci\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/zaven.co\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Effortless deployment of Azure CosmosDB JS functions with GitLab CI\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/zaven.co\/blog\/#website\",\"url\":\"https:\/\/zaven.co\/blog\/\",\"name\":\"Zaven Blog\",\"description\":\"Software development blog. Generative AI, web &amp; mobile applications.\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/zaven.co\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/zaven.co\/blog\/#\/schema\/person\/d1d670acafec5407b963815814a1db27\",\"name\":\"Leszek Janiszewski\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/zaven.co\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/7276b42cac05e7a4dcd2dfd2c56a7c40?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/7276b42cac05e7a4dcd2dfd2c56a7c40?s=96&d=mm&r=g\",\"caption\":\"Leszek Janiszewski\"},\"description\":\"Leszek is our mobile developer who mostly develops Android applications at Zaven. He also improves our company website and has recently been becoming an expert in React and Azure technologies. And the most important fact: Leszek took second place in the Zaven go-cart race! Congrats!\",\"sameAs\":[\"https:\/\/www.linkedin.com\/in\/leszek-janiszewski-423a53151\/\"],\"url\":\"https:\/\/zaven.co\/blog\/author\/leszek\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"CosmosDB JavaScript triggers. GitLab CI deployment to Azure | Zaven Blog","description":"If you have severe problems with Cosmos DB JavaScript implementation, get familiar with our tips. Particularly, if you work in several environments. Check it out!","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/zaven.co\/blog\/azure-cosmosdb-js-funcions-with-gitlab-ci\/","og_locale":"en_US","og_type":"article","og_title":"CosmosDB JavaScript triggers. GitLab CI deployment to Azure | Zaven Blog","og_description":"If you have severe problems with Cosmos DB JavaScript implementation, get familiar with our tips. Particularly, if you work in several environments. Check it out!","og_url":"https:\/\/zaven.co\/blog\/azure-cosmosdb-js-funcions-with-gitlab-ci\/","og_site_name":"Zaven Blog","article_published_time":"2018-11-28T14:26:22+00:00","article_modified_time":"2025-04-08T17:55:07+00:00","og_image":[{"width":1920,"height":1280,"url":"https:\/\/zaven.co\/blog\/wp-content\/uploads\/2018\/11\/Depositphotos_38768923_xl-2015-1920x1280.jpg","type":"image\/jpeg"}],"author":"Leszek Janiszewski","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Leszek Janiszewski","Est. reading time":"13 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/zaven.co\/blog\/azure-cosmosdb-js-funcions-with-gitlab-ci\/","url":"https:\/\/zaven.co\/blog\/azure-cosmosdb-js-funcions-with-gitlab-ci\/","name":"CosmosDB JavaScript triggers. GitLab CI deployment to Azure | Zaven Blog","isPartOf":{"@id":"https:\/\/zaven.co\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/zaven.co\/blog\/azure-cosmosdb-js-funcions-with-gitlab-ci\/#primaryimage"},"image":{"@id":"https:\/\/zaven.co\/blog\/azure-cosmosdb-js-funcions-with-gitlab-ci\/#primaryimage"},"thumbnailUrl":"https:\/\/zaven.co\/blog\/wp-content\/uploads\/2018\/11\/Depositphotos_38768923_xl-2015.jpg","datePublished":"2018-11-28T14:26:22+00:00","dateModified":"2025-04-08T17:55:07+00:00","author":{"@id":"https:\/\/zaven.co\/blog\/#\/schema\/person\/d1d670acafec5407b963815814a1db27"},"description":"If you have severe problems with Cosmos DB JavaScript implementation, get familiar with our tips. Particularly, if you work in several environments. Check it out!","breadcrumb":{"@id":"https:\/\/zaven.co\/blog\/azure-cosmosdb-js-funcions-with-gitlab-ci\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/zaven.co\/blog\/azure-cosmosdb-js-funcions-with-gitlab-ci\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/zaven.co\/blog\/azure-cosmosdb-js-funcions-with-gitlab-ci\/#primaryimage","url":"https:\/\/zaven.co\/blog\/wp-content\/uploads\/2018\/11\/Depositphotos_38768923_xl-2015.jpg","contentUrl":"https:\/\/zaven.co\/blog\/wp-content\/uploads\/2018\/11\/Depositphotos_38768923_xl-2015.jpg","width":5138,"height":3425},{"@type":"BreadcrumbList","@id":"https:\/\/zaven.co\/blog\/azure-cosmosdb-js-funcions-with-gitlab-ci\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/zaven.co\/blog\/"},{"@type":"ListItem","position":2,"name":"Effortless deployment of Azure CosmosDB JS functions with GitLab CI"}]},{"@type":"WebSite","@id":"https:\/\/zaven.co\/blog\/#website","url":"https:\/\/zaven.co\/blog\/","name":"Zaven Blog","description":"Software development blog. Generative AI, web &amp; mobile applications.","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/zaven.co\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/zaven.co\/blog\/#\/schema\/person\/d1d670acafec5407b963815814a1db27","name":"Leszek Janiszewski","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/zaven.co\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/7276b42cac05e7a4dcd2dfd2c56a7c40?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/7276b42cac05e7a4dcd2dfd2c56a7c40?s=96&d=mm&r=g","caption":"Leszek Janiszewski"},"description":"Leszek is our mobile developer who mostly develops Android applications at Zaven. He also improves our company website and has recently been becoming an expert in React and Azure technologies. And the most important fact: Leszek took second place in the Zaven go-cart race! Congrats!","sameAs":["https:\/\/www.linkedin.com\/in\/leszek-janiszewski-423a53151\/"],"url":"https:\/\/zaven.co\/blog\/author\/leszek\/"}]}},"_links":{"self":[{"href":"https:\/\/zaven.co\/blog\/wp-json\/wp\/v2\/posts\/1143","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/zaven.co\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/zaven.co\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/zaven.co\/blog\/wp-json\/wp\/v2\/users\/16"}],"replies":[{"embeddable":true,"href":"https:\/\/zaven.co\/blog\/wp-json\/wp\/v2\/comments?post=1143"}],"version-history":[{"count":46,"href":"https:\/\/zaven.co\/blog\/wp-json\/wp\/v2\/posts\/1143\/revisions"}],"predecessor-version":[{"id":69945,"href":"https:\/\/zaven.co\/blog\/wp-json\/wp\/v2\/posts\/1143\/revisions\/69945"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/zaven.co\/blog\/wp-json\/wp\/v2\/media\/1164"}],"wp:attachment":[{"href":"https:\/\/zaven.co\/blog\/wp-json\/wp\/v2\/media?parent=1143"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/zaven.co\/blog\/wp-json\/wp\/v2\/categories?post=1143"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/zaven.co\/blog\/wp-json\/wp\/v2\/tags?post=1143"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}