Tag: JavaScript

Dependency Injection with NextJS and TypeScript

Coming from a backend world, one thing that stands out when you start writing code in JavaScript, be it React, NextJS, Vue etc, is there's no great focus on code structure. Things like TypeScript bring back the concept of type checking that you'd be used to with a compiled language, and NextJS will give anyone familiar with ASP.NET MVC an alternative pattern for how a website should be constructed. But you can get the whole way through the NextJS tutorial without concepts like single responsibility, inversion of control or CQRS being mentioned once.

Now if your building a small site, maybe you can get away with not knowing or imlementing these things, but if you want to make code that's scalable or maintainable, it doesn't matter if you write in JavaScript or C# the same issues will exist but fortunately the same solutions do too.

Code Smells

Lets take a look at one of the functions on this site which gets the latest blog posts on the homepage.

import gql from 'graphql-tag';
import { Client, ApolClient } from '../prismicHelpers'

// Models
import { FeaturedPost } from "../../Models/FeaturedPost"

const latestPostsQuery = gql`
query latestPosts($category: String) {
allPosts (where : {category: $category}, first : 10, sortBy: post_date_DESC){
  edges {
    node {
      category {
        ... on Categories {
          name
        }          
      },
      title,
      image,
      post_date
      _meta {
        uid
      }
    }
  }
}
}
`;

export const getLatestPosts = async (category?: String) : Promise<FeaturedPost[]> => {
const queryOptions = {
  query: latestPostsQuery,
  variables: { category },
};

return new Promise((resolve, reject) => { ApolClient.query(queryOptions).then(response => {
  var posts: Array<FeaturedPost> = [];
  response.data.allPosts.edges.map((edge: { node: { title: { text: any; }[]; category: any; image: any; post_date: Date; _meta: { uid: any; }; }; }, key: any) => {
    posts.push({
      type: "post",
      title: edge.node.title[0].text,
      image: edge.node.image,
      uid: edge.node._meta.uid,
      category: edge.node.category?.name,
      postDate: edge.node.post_date
    })
  })
  resolve( posts);
}).catch(error => {
  reject(error);
});
});
};

This bit of code queries Prismic's GraphQL API to get the latest 10 articles for a category and then maps the result onto an internal FeaturedPost model.

Some good things about it:

  • The Prismic logic is abstracted away from the rest of the application by mapping the results to a model.
  • It follows single responsibility by doing just 1 job.
  • All config for the API (e.g. URI) are separated into a common function rather than being duplicated.

However even though I've seen countless JavaScript examples which don't even do these good things, if this were C# and I was reviewing a pull request, I'd say it smelt and needed changing.

Here's some bad things about it:

  • It's taking a dependency on Apollo Client, which while is a great client for doing GraphQL queries, at the rate JS frameworks come and go we can't say we'll never replace it and in a large application that has countless queries that would be a lot of code to update.
  • There's also no way anything can call this without also taking a dependency on it. That means I now have a hierarchy of at least 3 functions with each one dependent on the next. If I ever wanted to add unit tests to my project I'd have a big problem.

Dependency Injection using TSyringe

These issues can be solved using Dependency Injection. There's quite a few around but the one I've chosen is TSyringe (https://github.com/microsoft/tsyringe).

It's built by Microsoft, which gives me some confidence in the amount of QA that will have gone into it, but more importantly it works the way I expect a DI framework to work. There's a good chance this could be because I'm used to working in the Microsoft stack and it therefore has natural similarities to DI frameworks in their own languages.

How to add TSyringe to a NextJS project

There's nothing specifically NextJS about TSyringe, you could use it with any TypeScript/JavaScript project, it just happens that that's what my blog is built using. However it also had a few issues getting it to work and I couldn't any articles around which explained how.

To set it up...

First install the package tsyring and reflect-metadata using npm.

npm install --save tsyringe reflect-metadata

Modify your tsconfig.json file to include the following settings, which will allow the use of decorators in TypeScript.

{
"compilerOptions": {
  "experimentalDecorators": true,
  "emitDecoratorMetadata": true
}
}

Now there's a few more packages your going to need. As of NextJS 12, Babel is no longer used and has been replaced with SWC to provide faster compile times. However TSyringe and most other DI frameworks use decorators to function (hence the tsconfig.js setting to turn them on), but the setting for SWC to allow this set to false in NextJS and there's no way for you to provide your own config. Fortunately Babel is still supported and you can customise it. Hopefully a future version of NextJS will address this.

Install the following packages as dependencies.

npm install -D @babel/core @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators babel-plugin-transform-typescript-metadata

Add a .bablerc file to your project with the following settings.

{
"presets": ["next/babel"],
"plugins": [
  "babel-plugin-transform-typescript-metadata",
  ["@babel/plugin-proposal-decorators", { "legacy": true }],
  ["@babel/plugin-proposal-class-properties", { "loose": true }]
]
}

Finally add this import to your _app.tsx file.

import "reflect-metadata";

Using TSyringe

We're now ready to convert the code I had before into something more maintainable.

First I'm going to create a GraphQL Client interface which all my queries will use when they want to call a graph API. This has one function called query, which my functions will pass the graph syntax too along with a variables object.

import { DocumentNode } from "graphql";

export interface graphClient {
query(query: DocumentNode, variables?: {}): any;
}

With this interface I can now turn my getLatestPosts function into a class with a constructor that will take in the instance of graphClient.

import { inject, injectable } from "tsyringe";
import { iGetLatestPosts } from "./iGetLatestPosts";
import gql from "graphql-tag";
import { graphClient } from "../iGraphQl";

// Models
import { FeaturedPost } from "../../Models/FeaturedPost";

@injectable()
export class getLatestPosts implements iGetLatestPosts {
graphClient: graphClient;

constructor(@inject("graphClient") private graphClientParam: graphClient) {
  this.graphClient = graphClientParam;
}

private latestPostsQuery = gql`
  query latestPosts($category: String) {
    allPosts(
      where: { category: $category }
      first: 10
      sortBy: post_date_DESC
    ) {
      edges {
        node {
          category {
            ... on Categories {
              name
            }
          }
          title
          image
          post_date
          _meta {
            uid
          }
        }
      }
    }
  }
`;

public getLatestPosts = async (
  category?: String
): Promise<FeaturedPost[]> => {
  return new Promise((resolve, reject) => {
    this.graphClient
      .query(this.latestPostsQuery, { category })
      .then((response: any) => {
        var posts: Array<FeaturedPost> = [];
        response.data.allPosts.edges.map(
          (
            edge: {
              node: {
                title: { text: any }[];
                category: any;
                image: any;
                post_date: Date;
                _meta: { uid: any };
              };
            },
            key: any
          ) => {
            posts.push({
              type: "post",
              title: edge.node.title[0].text,
              image: edge.node.image,
              uid: edge.node._meta.uid,
              category: edge.node.category?.name,
              postDate: edge.node.post_date,
            });
          }
        );
        resolve(posts);
      })
      .catch((error: any) => {
        reject(error);
      });
  });
};
}

Some things to note in this new class.

  • It's also now implementing an interface so that it can instantiated using DI.
  • The @injectable decorator allows TSyringe to inject the dependencies at runtime.
  • The constructor is decorating a parameter with @inject("graphClient") which means that parameter will be injected at runtime with whatever is configured against the graphClient token.
  • There are imports from tsyringe.
  • There are no references to the implementation of graphClient.
  • My function now has zero dependencies on Apollo Client and doesn't even know it's being used.

My implementation of graphClient looks like this.

import { autoInjectable } from "tsyringe";
import { DocumentNode } from "apollo-link";
import ApolloClient from "apollo-client";
import { NormalizedCacheObject } from "apollo-cache-inmemory";
import { graphClient } from "./iGraphQl";

@autoInjectable()
export class apolloGraphClient implements graphClient {
apolloClient: ApolloClient<NormalizedCacheObject>;

constructor(apolloClient: ApolloClient<NormalizedCacheObject>) {
  this.apolloClient = apolloClient;
}

public query = async (query: DocumentNode, variables?: {}): Promise<any> => {
  const queryOptions = {
    query: query,
    variables: variables,
  };

  return new Promise((resolve, reject) => {
    this.apolloClient
      .query(queryOptions)
      .then((response: any) => {
        resolve(response);
      })
      .catch((error: any) => {
        reject(error);
      });
  });
};
}

Essentially all this function does is pass the parameters to the query function to an Apollo Client's query function. The Apollo Client itself is also being injected!

You may have expected this file to also instantiate the Apollo Client, and it could have, but I've gone to the extreme and the single purpose of this file is to act as a bridge between the business logic queries and what client is being used, so for that reason its injected.

You'll also notice that this time I'm decorating the class with @autoInjectable() and there is no decorator on the constructor parameter. More on this in a bit.

The homepage page for this site now looks like this.

import Head from "next/head";

import React, { useState } from "react";
import { container } from "tsyringe";
import { GetStaticProps } from "next";
import Layout from "../layouts/layout";
import { FeaturedRow1, FeaturedRow1Model } from "../components/featured-row-1";
import SectionHeading from "../components/section-heading";
import { iGetLatestPosts } from "../utils/queries/iGetLatestPosts";

export default function Home({
latestPosts,
webDevelopmentPosts,
sitecorePosts,
devOpsPosts,
}: {
latestPosts: FeaturedRow1Model;
webDevelopmentPosts: FeaturedRow1Model;
sitecorePosts: FeaturedRow1Model;
devOpsPosts: FeaturedRow1Model;
}) {
return (
  <Layout>
    <Head>
      <title>Hi My Name Is Tim</title>
    </Head>
    <SectionHeading heading="Latest Posts" link="blog"></SectionHeading>
    <FeaturedRow1 posts={latestPosts}></FeaturedRow1>
    <SectionHeading heading="Web Development" link="web-development"></SectionHeading>
    <FeaturedRow1 posts={webDevelopmentPosts}></FeaturedRow1>
    <SectionHeading heading="Sitecore" link="sitecore"></SectionHeading>
    <FeaturedRow1 posts={sitecorePosts}></FeaturedRow1>
    <SectionHeading heading="Devops" link="devops"></SectionHeading>
    <FeaturedRow1 posts={devOpsPosts}></FeaturedRow1>
  </Layout>
);
}

export const getStaticProps: GetStaticProps = async () => {
// Resolve interface for iGetLatestPosts
const instance = container.resolve<iGetLatestPosts>("iGetLatestPosts");

const latestPosts = await instance.getLatestPosts();
const webDevelopmentPosts = await instance.getLatestPosts("X8kFhxIAACcAn9oY");
const devOpsPosts = await instance.getLatestPosts("X8kFlRIAACkAn9pa");
const sitecorePosts = await instance.getLatestPosts("X8kFeBIAACkAn9nV");

return {
  props: {
    latestPosts: latestPosts,
    devOpsPosts: devOpsPosts,
    sitecorePosts: sitecorePosts,
    webDevelopmentPosts: webDevelopmentPosts,
  },
};
};

Pages in NextJS TypeScript don't use classes, so we can't do constructor injection to get the instance of our getLatestPosts query class. Instead we are using container.resolve<iGetLatestPosts>("iGetLatestPosts") to get the instance to token name iGetLatestPosts from the DI container.

Lastly in the _app.tsx file I am registering the classes on the container. I'm only including the relevant bit of the file here.

container.registerInstance(
ApolloClient,
new ApolloClient({
  link: PrismicLink({
    uri: prismicGraphUri,
    repositoryName: prismicRepoName,
  }),
  cache: new InMemoryCache({ fragmentMatcher }),
})
);

container.register(&quot;graphClient&quot;, apolloGraphClient);
container.register(&quot;iGetLatestPosts&quot;, getLatestPosts);

For the Apollo Client I am using register instance to register a specific instance and creating it at the same time. Notice the first parameter is the class name.

For the graph client and getLatestPosts query I am using the register method and rather than creating the instance of my implementation, just passing the class as the second parameter. The framework will handle creating an instance of them for me.

Notice the first parameter for the second two are strings rather than the actual interfaces. These are token names that the container will use to reference the instance value. With the Apollo Client, the framework will figure out the token name when it adds it to the container, but it can't do the same for an interface (if you try you will get an interface cannot be used as a type error) so you have to provide the token name instead. This is also the reason why the graph client implementation didn't need to use a string to inject the class instance in the constructor, whereas the other places did.

Personally I feel this is a weakness in the framework as it also means there is no type checking either when registering or resolving from the container.

Summary

We have seen that using JavaScript doesn't prevent us from using the same concepts as we would with any other more traditional backend language.

We've also seen how a NextJS application while slightly more awkward, can still be set up to use dependency injections.

Finally we've had a look at how to actually configure some code to have complete separation between logic within a solution.

Typescript: What to do when your third party NPM module doesn't have type declarations

As a developer who has probably spent slightly more time doing backend work than front end, and also who's front end work has been more about SPAs than making things look pretty, I really like what TypeScript brings to writing JavaScript. While it's often a nice break from C# to do write some code that doesn't require you to go and define every object there obviously a lot of benefits to do this (otherwise we would just make everything dynamic in C'#). Having file name's show up red in VS Code when there's a type error is a really nice feature.

However if you want to set your compiler options to strict, you will more than likely run into an issue with third part code coming from NPM. In the form of:

Type error: Could not find a declaration file for module MODULE NAME HERE

Here's a screen grab of the compiler error from my build server...

It's offering a nice line of hope by suggesting I run an npm command to add in the types. Sometime this works, but as not everyone feels the need to adopt TypeScript you'll find there's times when it doesn't.

The only solution to this is to write your own.

Type Definitions are stored in the node_modules folder, but as that isn't something you'll want to be adding to source control you'll need another folder. I call mine @types and have it in the root. Within that folder you then need a folder for each module requiring type definitions. The name of each folder should match the module name.

Here's mine:

To make the actual declaration file you'll need to name it index.d.ts and place it in the folder you just created. You can read more about how declaration files should be structured here, however there's an issue with writing a declaration file for a third party module.

1. It's very confusing to work out everything that should go in it

2. It's not your module so it might change

Unless your planning on helping develop the module and create the official type declarations, I suggest an easier option.

Create a file that looks like this:

/// <reference types="node" />

declare module 'module name here';

This is enough to satisfy the compiler. You won't get accurate types for the module, but they would only ever be as good as your interpretation anyway. This will at least unlock the ability for the rest of your application to continue functioning for strict type checking.

Redirecting to login page with AngularJs and .net WebAPI

So here's the scenario, you have a web application which people log into and some of the pages (like a dashboard) contain ajax functionality. Inevitably the users session expires, they return to the window change a filter and nothing happens. In the background, your JavaScript is making http calls to the server which triggers an unauthorised response. The front end has no way to handle this and some errors appear in the JS console.

A few things are actually combining to make life hard for you here. Lets take a look at each in more detail.

WebAPI and the 301 Response

To protect your API's from public access a good solution is to use the Authorize attribute. i.e.

public ActionResult GetDashboardData(int foo)
{
 // Your api logic here
          
}

However chances are your solution also has a login page configured in your web.config so that your regular page controller automatically trigger a 301 response to the login page.

<authentication mode="Forms">
    <forms timeout="30" loginUrl="/account/sign-in/" />
  </authentication>

So now what happens, is instead or responding with a 401 Unauthorised response, what's actually returned is a 301 to the login page.

With an AJAX request from a browser you now hit a second issue. The browser is making an XMLHttpRequest. However if that request returns a 301, rather than returning it your JavaScript code to handle, it "helpfully" follows the redirect and returns that to your JavaScript. Which means rather than receiving a 301 redirect status back, your code is getting a 200 Ok.

So to summarise your API was set up to return a 401 Unauthorised, that got turned into a 301 Redirect, which was then followed and turned into a 200 Ok before it gets back to where it was requested from.

To fix this the easiest method is to create are own version of the AuthorizedAttribute which returns a 403 Forbidden for Ajax requests and the regular logic for anything else.

using System;
using System.Web.Mvc;

namespace FooApp
{
  [AttributeUsage(AttributeTargets.Method)]
  public class CustomAuthorizeAttribute : AuthorizeAttribute
  {
      protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
      {
          if (filterContext.HttpContext.Request.IsAjaxRequest())
          {
              filterContext.Result = new HttpStatusCodeResult(403, "Forbidden");
          }
          else
          {
              base.HandleUnauthorizedRequest(filterContext);
          }
      }
  }
}

Now for any Ajax requests a 403 is returned, for everything else the 301 to the login page is returned.

Redirect 403 Responses in AngularJs to the login page

As our Ajax request is being informed about the unauthorised response, it's up to our JavaScript code trigger the redirect in the browser to the login page. What would be really helpful would be to define the redirect logic in one place, rather than adding this logic to every api call in our code.

To do this we can use add an interceptor onto the http provider in angular js. The interceptor will inspect the response error coming back from the XmlHttpRequest and if it has a status of 401, use a window.locator to redirect the user to the login page.

app.factory('httpForbiddenInterceptor', ['$q', 'loginUrl', function ($q, loginUrl) {
  return {
      'responseError': function (rejection) {
          if (rejection.status == 403) {
              window.location = loginUrl;
          }
          return $q.reject(rejection);
      }
  };
}]);

app.config(['$httpProvider', function ($httpProvider) {
  $httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
  $httpProvider.interceptors.push('httpForbiddenInterceptor');
}]);

You'll notice a line updating the headers. This is to make the IsAjaxRequest() method on the api recognise the request as being Ajax.

Finally you'll also notice the loginUrl being passed into the interceptor. As it's not a great idea to have strings like urls littered around your code, this is using a value recipe to store the url. The code to do this is follows:

app.value('loginUrl', '/account/sign-in?returnurl=/dashboard/');