Quick1: JS, Remove duplicates from an array

There are many ways to do this by looping over the array, then using something like .some() or .findIndex().

How about this as an elegant and simple solution?

[...new Map(users.map((item) => [item.id, item])).values()]

The use case is a collection of aggregated data that could have multiple copies of a user. I needed to remove duplicates:

const users = [
  {id: "1234", name: "kirk"},
  {id: "6789", name: "spock"},
  {id: "1234", name: "kirk"},
  {id: "1234", name: "kirk"},
  {id: "rtyu", name: "bones"},
]

console.log([...new Map(users.map((item) => [item.id, item])).values()])

Copy and paste the code below into your browser console pane to see it work


const users = [
  {id: "1234", name: "kirk"},
  {id: "6789", name: "spock"},
  {id: "1234", name: "kirk"},
  {id: "1234", name: "kirk"},
  {id: "rtyu", name: "bones"},
]

[...new Map(users.map((item) => [item.id, item])).values()]

Node.js, Express.js with Typescript dream boilerplate project

I have been using Node.js with Express.js for just over a year now and Typescript for 8 months building and maintaining micro services for an SME business operating globally.

The project is at: https://github.com/daveKoala/typescript-api-starter.git

Of the 1/2 dozen I’ve created from scratch each has grown in sophistication. I now know what I want for each REST api service (in no real order):

  • Node.js with Express.js
  • Multiple environments
  • Typescript
  • Unit testing (with tests alongside the code and not in ./tests/ directory)
  • External cache
  • A ‘developer’ mode that restarts when saving
  • Debugging so I don’t have to rely on console.log
  • All end points versioned and documented
  • Authentication and Authorisation middleware
  • Custom errors
  • Re-try connections if they initially fail
  • Project auto saves and lints
  • A CI/CD pipeline that runs test and checks for external package vulnerabilities.

I am nearly there…

Node.js, Express.js and Typescript. Not much to say about this that has not already been said.

Multiple Environments: This is a bit trickier as conventional wisdom is that env values are not stored within the repo. This makes perfect sense, however the projects I am working with have 3 people at most and keeping the env within the private repo lets us quickly create new services and keep all settings, pipelines etc together with minimal fuss. It was a decision we made as a group 18 months ago and has paid off. I know there will be eye rolling at this tho’.

Unit Testing: I using Mocha.js and Chai.js no real reason apart from it works for me. There are 2 specific requirements. The first being that test scripts sit alongside the code being tested and the second is the option to publish test results. This project does both.

External Cache: Middleware on routes that intercepts the request, hashes the url and body to make an ID then queries the cache. A null response passes the request to the controller and caches the results.

‘Developer mode’: Easy with Nodemon and scripts within the package.json file. There are a few other useful scripts including a “lazy script” that runs tests, linting, audits and a build process.

Debugging: I’ll leave that to Visual Studio Code and commit any settings files.

All end points versioned and documented: Using Swagger and OpenDoc to create nice api docs. Added advantage is that you can link/download to the OpenDocs JSON into Postman to create collections.

Versioning: I opted for the “version via the request header” approach. Why? I like that the urls don’t change and because it is strict. Strict as in, if you fail to request a version you get feedback telling you what you need to do. The main thing about versioning is declaring a contract and sticking to it.

Authentication and Authorisation: Separate middleware that can leverage different methods specified by the project.

Custom errors: Easy enough, create meaningful error messages and responses. They also accept plugins for external logging.

Re-try connections if they initially fail: Also known as a Circuit Breaker pattern.

Project auto saves and lints: Let Visual Studio handle this and commit settings to the project.

CI/CD Pipeline: Convention over configuration and making use of files like Azure Pipelines YAML files to describe and run CI/CD processes.

This is an ongoing project and can be found at: https://github.com/daveKoala/typescript-api-starter.git

Quick1: JS + Vue.js pause before

Problem:

I had a large list that a user could reorder by dragging and dropping. All work beautifully.

Except persisting the changes to the database. I did not want the an API call for every drop action or a button that the user clicked that said, “Save list”.

Solution:

Its a Vue.js project and the drag and drop had two events called “start” and “end”. These would call the corresponding methods below.

“endDrag” starts a timer that will run the supplied method in 1.5 seconds after the dragging has stopped. If the user starts “dragging” again the timer is cancelled

methods: {
    endDrag(): void {
      this.timer = setTimeout(this.distpatchOrderedList, 1500);
      this.drag = false;
    },
    startDrag(): void {
      clearTimeout(this.timer);
      this.drag = true;
    },
   distpatchOrderedList(): void {
     // Do some magic
   }
}

Quick1: Typescript

I’ve been using Typescript for 12 months and every now and then I get blocked. I used to do a fair bit of ‘typescript ignore’ in my code just to keep things moving and depend on my tests to throw up mistakes.

This I think ha caught me out a few times in different circumstances.

interface Options {
  id: string;
  name: string;
}
const options: Options = {id: "qwerty", name: "andy pandy"}

Object(options).forEach(key => {
  localOptions[key] = options[key]
});

The above would not compile and give the following message (note if I set the line to be ignored the code still worked)

Element implicitly has an ‘any’ type because expression of type ‘string’ can’t be used to index type ‘xxxx’. No index signature with a parameter of type ‘string’ was found on type ‘xxxx’

So how to comply with Typescript?

I found two options. One went about extending the object constructor and adding overloads the second is type-casting. Extending the object constructor is probably more “correct” but type-casting worked and when you read the code it simpler to understand (IMHO)

interface Options {
  id: string;
  name: string;
}
const options: Options = {id: "qwerty", name: "andy pandy"}

Object(options).forEach(key => {
  localOptions[key as keyof Options] = options[key as keyof Options]
});

Does it work and does it address the issue? Yes it does.

Javascript simple date string comparison

I need to check if a date string is within a date range.

// Set up
const dateRange = ["2020-07-30", "2020-07-20"];

let n = 0;

const collection = [
{ "createdOn": "2020-07-01", title: `title # ${ n++ }` },
{ "createdOn": "2020-07-10", title: `title # ${ n++ }` },
{ "createdOn": "2020-07-20", title: `title # ${ n++ }` }, // Include
{ "createdOn": "2020-07-21", title: `title # ${ n++ }` }, // Include
{ "createdOn": "2020-07-21", title: `title # ${ n++ }` }, // Include
{ "createdOn": "2020-07-30", title: `title # ${ n++ }` }, // Include
{ "createdOn": "2020-07-30", title: `title # ${ n++ }` }, // Include
{ "createdOn": "2020-07-31", title: `title # ${ n++ }` },
];

// Solution
const filter = (collection, dateRange) => {
    const dates = [...dateRange].sort(); // Sorts the date range: from -> to
    return collection.filter(item => item.createdOn >= dates[0] && item.createdOn <= dates[1]); // returns either array of objects or an empty array
}

console.table(filter(collection, dateRange));

┌─────────┬──────────────┬─────────────┐ 
│ (index) │ createdOn    │ title       │ 
├─────────┼──────────────┼─────────────┤ 
│ 0       │ '2020-07-20' │ 'title # 2' │ 
│ 1       │ '2020-07-21' │ 'title # 3' │ 
│ 2       │ '2020-07-21' │ 'title # 4' │ 
│ 3       │ '2020-07-30' │ 'title # 5' │ 
│ 4       │ '2020-07-30' │ 'title # 6' │ 
└─────────┴──────────────┴─────────────┘ 

JavaScript dates

Every time I have anything to do with manipulating dates in JavaScript I need to stop and refresh my memory. Yes, there are amazing packages from Moment.js and Date-fns.

But why should I import a library just to a few simple things?

Here are my notes on dates (it is not an exhaustive list).

Time zones

There are 24 time zones mix in that many also have day light saving which adds/subtracts an hour. When a country does this is not constant around the world and is not fixed

So, in JavaScript there are two times:

  • Local = the time of the computer the code is running on
  • UTC = roughly the same as Greenwich Mean Time (GMT)

Date methods in JavaScript return Local time

In practice

Here I need to create a date range array. The range covers 30 days

const autoDays = () => {
      const daysInThePast = 30;
      const today = new Date();
      const pastDate = new Date(today);
      pastDate.setDate(today.getDate() - daysInThePast);
      return [
        pastDate.toISOString().substring(0, 10),
        today.toISOString().substring(0, 10)
      ];
    }

Explanation

  • Two ‘new Date()’ are used to avoid unwanted mutation
  • ‘today.getDate()’ returns the number part of the date. E.g. 29 in 29th July 2020
  • ‘pastDate.setDate(today.getDate() – daysInThePast)’ sets the date number. Notice how the number could be a negative or positive number. E.g. -10 or +36
  • JavaScript will correct the date to make sense. E.g. 36th July would become 5th August

Vue.js – Azure Pipelines

I had a requirement to automate the CI/CD pipeline of an existing Vue.js project.

What was asked…

  1. On any merge into the branch “releases/dev
  2. Run all unit tests
  3. Build for the development/staging environment
  4. Copy the files to Azure Blob storage so they can be used as a SPA

The project repo was already in Azure Dev Ops

trigger:
  - releases/dev

pool:
  vmImage: "ubuntu-latest"

steps:
  - task: NodeTool@0
    inputs:
      versionSpec: "12.x"
    displayName: "Install Node.js"

- script: |
    npm install
    npm run test:unit
  displayName: "npm install"

- script: |
    npm run test:unit
  displayName: "npm run test:unit"

- script: |
    npm run build:dev
  displayName: "npm run build:dev"

- task: AzureCLI@2
  displayName: "Copy files to blob storage"
  inputs:
    azureSubscription: 'Faux-Development-ltd'
    scriptType: 'bash'
    scriptLocation: 'inlineScript'
    inlineScript: 'az storage blob upload-batch -d \$web --account-   name "blobStoreName" -s "dist" --connection-string "DefaultEndpointsProtocol=https;AccountName=xxxxxx;AccountKey=123456789123456789/qwerty/qwertybangbang==;EndpointSuffix=core.windows.net"'

The only sticking points where.

The blob store container is called $web which look like a variable in the shell script so I had to use \$web

JS does a string/number exist within a nested array collection?

I have the following and need to test if a user id is present anywhere in the collection.

const meetingCollection = [
  { id: "2", userIds: [12, 34, 55, 6, 7, 43543, 45345, 545] },
  { id: "21", userIds: [3, 425, 5, 33, 7, 68, 76, 99, 66, 99] },
  { id: "22", userIds: [767, 5654756, 5658, 86, 45, 23456, 666] },
  { id: "23", userIds: [6, 43562, 5645747, 4654, 2577, 4345, 45777] },
  { id: "24", userIds: [45645, 124, 4, 435, 6, 7755, 7, 8] },
  { id: "25", userIds: [1, 2, 455, 647, 753] },
]
const queryString = 7755;

meetingCollection.some(m => m.userIds.some(userId => userId === queryString))

Array.prototype.some() tests the method against each “row” of the collection and returns true on the first hit and then stops. If you have a 1000 elements and the method returns true on the 10th iteration .some() stops looping over the collection and returns true.

If a method fails to return true then false is returned.

The above example I’ve nested .some()

Try this in the console….

const meetingCollection = [
  { id: "2", userIds: [12, 34, 55, 6, 7, 43543, 45345, 545] },
  { id: "21", userIds: [3, 425, 5, 33, 7, 68, 76, 99, 66, 99] },
  { id: "22", userIds: [767, 5654756, 5658, 86, 45, 23456, 666] },
  { id: "23", userIds: [6, 43562, 5645747, 4654, 2577, 4345, 45777] },
  { id: "24", userIds: [45645, 124, 4, 435, 6, 7755, 7, 8] },
  { id: "25", userIds: [1, 2, 455, 647, 753] },
]

const findId = (queryString) => {
  return meetingCollection.some(m => m.userIds.some(userId => userId === queryString))
}

console.log(findId(2577)); // true
console.log(findId(257)); // false

JS rounding numbers

I have a requirement to be able to round numbers to either a whole number or to a specified number of decimal places….

Every now and then i find myself looking over my current solution to a problem when applying it in a new project. In this case it was very simple statistics and mean values.

For example:

const dec = [2, 678, 65.67467, 10.5452453453, 1, 0];
// Required outcome to 3 decimal places
// 2, 678, 65.675, 10.545, 1, 0

// Or to 2 decimal places
// 2, 678, 65.67, 10.55, 1, 0

My solution:

const round = (n, decimal = 2) => {
    return Math.round(n * Math.pow(10, decimal)) / Math.pow(10, decimal);
};
// Quick test
dec.forEach(n => {
console.log(round(n, 3));
});

dec.forEach(n => {
console.log(round(n));
});

As an aside: I need to have a set of numbers rounded and set with 2 fixed decimal places. So the same data set would be:

const dec = [2, 678, 65.67467, 10.5452453453, 1, 0];

// Required response would be
// 2.00, 678.00, 65.67, 10.55, 1.00, 0.00

There is the Number.toFixed() method

dec.forEach(n => console.log(n.toFixed(2)));

Finding, replacing, deleting and adding to a nested collection

Problem: I have an object that has a field call modules that is and array of activities. Activities is itself a complex and many nested object. I need to hunt for activities by their IDs and replace, delete or insert between existing objects

// Replace object in collection
Object.keys(data.modules).forEach(modKey => {
  Object.keys(data.modules[modKey].activities).forEach(activityKey => {
    if (data.modules[modKey].activities[activityKey].id === newActivity.id) {
      data.modules[modKey].activities.splice(activityKey, 1, newActivity);
    }
  });
});
// Remove object in collection
Object.keys(data.modules).forEach(modKey => {
  Object.keys(data.modules[modKey].activities).forEach(activityKey => {
    if (
      data.modules[modKey].activities[activityKey] &&
      data.modules[modKey].activities[activityKey].id === newActivity.id
    ) {
      data.modules[modKey].activities.splice(activityKey, 1);
    }
  });
});
// Insert object at index
const moudleIndex = 0;
const activityIndex = 2;

Object.keys(data.modules).forEach(modKey => {
  if (modKey == moudleIndex) {
    Object.keys(data.modules[modKey].activities).forEach(activityKey => {
      if (activityKey == activityIndex) {
        data.modules[modKey].activities.splice(activityKey, 0, newActivity);
      }
    });
  }
});