Skip to content

Getting Authenticated Images in Angular

This article was written over 18 months ago and may contain information that is out of date. Some content may be relevant but please refer to the relevant official documentation or available resources for the latest information.

Getting authenticated images in Angular

Let's imagine the following situation: We are working on an application which handles contracts between clients and sales representatives from a particular company.

For example, an insurance company could require us to develop a secure application where damage reports can be uploaded, and then the insurance agent, who has access rights to deal with such reports, can check the uploaded photos. These uploaded photos can be photos of damaged cars where the license plate is, and photos of the car's registration papers. These photos can contain sensitive data.

So the application is almost ready. One of the last features is to display these uploaded photos to everybody who has access to that particular insurance report. The application is set up in a way that allows us to download the images from a specific endpoint: /api/reports/{reportId}/{imageId}. We have set up our layout, have generated URLs for all the images that we need to request, and put them into the img tags.

However, they don't show up, because when they are fetched, they never hit our HttpInterceptor that sets the Authorization header on the request.

Introducing @this-dot/ng-utils UseHttpImageSource pipe

We came up with the idea of using a pipe to solve the above problem. We decided to include it as the first element in a collection of useful utilities for Angular. We called it @this-dot/ng-utils.

How to solve the problem?

If we send our request using Angular's HttpClient, it will hit our HttpInterceptor, which will, in turn, attach the Authorization header to the request. To avoid putting this logic into our services and/or components, we are going to implement a pipe.

@Pipe({
  name: 'useHttpImgSrc',
  pure: false,
})
export class UseHttpImageSourcePipe implements PipeTransform {

  constructor(private httpClient: HttpClient,
              private domSanitizer: DomSanitizer,
              private cdr: ChangeDetectorRef) {
  }

  transform(imagePath: string): string | SafeUrl {
    // our logic will come here
    return imagePath;
  }

}

We immediately know that we are going to need the HttpClient for getting the image, the DomSanitizer, so we will be able to use the bypassSecurityTrustUrl method to allow the returned blob to be displayed.

We do know that the endpoints we call will return safe images, so that is why we trust the returned values. We will also need the ChangeDetectorRef because this process is async, and we are going to trigger change detection manually. The pipe itself is not pure, because the returned value will change eventually, so we need to get the value from the transform on every change detection cycle.

We also need to keep in mind that whenever the input value changes, a new request needs to be sent out. That is why we are going to use a BehaviorSubject as the base of our async subscription. Let's also use the ngOnDestroy lifecycle hook to tear down the subscription when the component that has our pipe instance destroys.

@Pipe({
  name: 'useHttpImgSrc',
  pure: false,
})
export class UseHttpImageSourcePipe implements PipeTransform, OnDestroy {
  private subscription = new Subscription();
  private transformValue = new BehaviorSubject<string>('');

  private latestValue!: string | SafeUrl;

  constructor(private httpClient: HttpClient,
              private domSanitizer: DomSanitizer,
              private cdr: ChangeDetectorRef) {
    // every pipe instance will set up their subscription
    this.setUpSubscription();
  }

  // ...

  transform(imagePath: string): string | SafeUrl {
    // we emit a new value
    this.transformValue.next(imagePath);

    // we always return the latest value
    return this.latestValue;
  }

  ngOnDestroy(): void {
    this.subscription.unsubscribe();
  }

  private setUpSubscription(): void {
    const transformSubscription = this.transformValue
      .asObservable()
      .pipe(
        // we filter out empty strings and falsy values
        filter((v): v is string => !!v),
        // we don't emit if the input hasn't changed
        distinctUntilChanged(),
        // Our HttpClient logic will come here
        // 
        tap((imagePath: string | SafeUrl) => {
        // we set the latestValue property of the pipe
        this.latestValue = imagePath;
        // and we mark the DOM changed, so the pipe's transform method is called again
        this.cdr.markForCheck();
      })
      )
      .subscribe();
    this.subscription.add(transformSubscription);
  }
}

Let's walk through what happens. When the pipe is initialised, it sets up the subscription to our transformValue, BehaviorSubject. This subscription will only be initialised once per pipe instance, and we unsubscribe from it in the ngOnDestroy() lifecycle hook.

The logic will happen mainly inside our setUpSubscription() method, where we filter out falsy values. We use the distinctUntilChanged() operator to check if the current transformValue is different from the previous one. If it is, it emits the new value. And finally, the transform method returns the latestValue, which is stored on the pipe.

We are going to implement our HttpClient logic there. Right now it just sets the latestValue property and triggers change detection. Let's get our images using the HttpClient.

@Pipe({
  name: 'useHttpImgSrc',
  pure: false,
})
export class UseHttpImageSourcePipe implements PipeTransform, OnDestroy {
  // ...
  private setUpSubscription(): void {
    const transformSubscription = this.transformValue
      .asObservable()
      .pipe(
        filter((v): v is string => !!v),
        distinctUntilChanged(),
        // we use switchMap, so the previous subscription gets torn down 
        switchMap((imagePath: string) => this.httpClient
          // we get the imagePath, observing the response and getting it as a 'blob'
          .get(imagePath, { observe: 'response', responseType: 'blob' })
          .pipe(
            // we map our blob into an ObjectURL
            map((response: HttpResponse<Blob>) => URL.createObjectURL(response.body)),
            // we bypass Angular's security mechanisms
            map((unsafeBlobUrl: string) => this.domSanitizer.bypassSecurityTrustUrl(unsafeBlobUrl)),
            // we trigger it only when there is a change in the result
            filter((blobUrl) => blobUrl !== this.latestValue),
          )
        ),
        tap((imagePath: string | SafeUrl) => {
          this.latestValue = imagePath;
          this.cdr.markForCheck();
        })
      )
      .subscribe();
    this.subscription.add(transformSubscription);
  }
}

We subscribe to our httpClient.get() method inside a switchMap operator, so we unsubscribe from the previous subscription if the image path passed into our transform method changes. We set up our get request to return a blob which we then convert into an ObjectURL, and we bypass the safety mechanisms of Angular. When the change detection cycle is triggered, this sanitised blob will be returned as the latestValue, and the image gets displayed. And when we check the network tab of the dev tools, we can see that our Authorization header is set on the image request.

Our pipe in our template:

  <img width="200px" [src]="'assets/images/success.png' | useHttpImgSrc" />
With authorization header

Some user experience improvements

Although our images now display on the page, we got a requirement to make them stateful. A loading image should be displayed before the actual image is loaded, and if the request has an error, another image should be displayed. Let's set up the loading image logic in our pipe, using an optional parameter in the transform() method.

@Pipe({
  name: 'useHttpImgSrc',
  pure: false,
})
export class UseHttpImageSourcePipe implements PipeTransform, OnDestroy {
  private subscription = new Subscription();
  private loadingImagePath!: string;
  private latestValue!: string | SafeUrl;
  private transformValue = new BehaviorSubject<string>('');

  // ...

  transform(
    imagePath: string,
    loadingImagePath?: string
  ): string | SafeUrl {
    this.setLoadingImagePath(loadingImagePath);
    // ...

    this.transformValue.next(imagePath);
    // return the loading image while there is no value present
    return this.latestValue || this.loadingImagePath;
  }

  // ...

  private setLoadingImagePath(
    loadingImagePath?: string
  ): void {
    // if it is already set we do nothing
    if (this.loadingImagePath) {
      return;
    }
    this.loadingImagePath = loadingImagePath;
  }

}

The latestValue property is only falsy before the actual image arrives. We created the setLoadingImagePath() method, which if provided, sets up a path for the loading placeholder image. Let's set up our template for two images, one which would surely fail, the other one will be loaded, but both of them will display the loading screen.

  <img width="200px" [src]="'assets/images/success.png' | useHttpImgSrc:'assets/images/loading.png'" />
  // ...
  <img width="200px" [src]="'assets/images/notfound.png' | useHttpImgSrc:'assets/images/loading.png'" />
Images are loading

Let's make the error image working as well. We are going to pass it as a second optional parameter. Let's update our template first.

  <img width="200px" [src]="'assets/images/success.png' | useHttpImgSrc:'assets/images/loading.png':'assets/images/error.png'" />
  // ...
  <img width="200px" [src]="'assets/images/notfound.png' | useHttpImgSrc:'assets/images/loading.png':'assets/images/error.png'" />

Let's update our pipe's implementations as well.

@Pipe({
  name: 'useHttpImgSrc',
  pure: false,
})
export class UseHttpImageSourcePipe implements PipeTransform, OnDestroy {
  private subscription = new Subscription();
  private loadingImagePath!: string;
  private errorImagePath!: string;
  private latestValue!: string | SafeUrl;
  private transformValue = new BehaviorSubject<string>('');

  // ...

  transform(
    imagePath: string,
    loadingImagePath?: string,
    errorImagePath?: string
  ): string | SafeUrl {
    this.setLoadingAndErrorImagePaths(loadingImagePath, errorImagePath);
    if (!imagePath) {
      return this.errorImagePath;
    }

    this.transformValue.next(imagePath);
    return this.latestValue || this.loadingImagePath;
  }

  // ...

  private setUpSubscription(): void {
    const transformSubscription = this.transformValue
      .asObservable()
      .pipe(
        filter((v): v is string => !!v),
        switchMap((imagePath: string) => this.httpClient
          .get(imagePath, { observe: 'response', responseType: 'blob' })
          .pipe(
            map((response: HttpResponse<Blob>) => URL.createObjectURL(response.body)),
            map((unsafeBlobUrl: string) => this.domSanitizer.bypassSecurityTrustUrl(unsafeBlobUrl)),
            filter((blobUrl) => blobUrl !== this.latestValue),
            // if the request errors out we return the error image's path value
            catchError(() => of(this.errorImagePath))
          )
        ),
        tap((imagePath: string | SafeUrl) => {
          this.latestValue = imagePath;
          this.cdr.markForCheck();
        })
      )
      .subscribe();
    this.subscription.add(transformSubscription);
  }

  // ...

  private setLoadingAndErrorImagePaths(
    loadingImagePath: string,
    errorImagePath: string
  ): void {
    if (this.loadingImagePath && this.errorImagePath) {
      return;
    }
    this.loadingImagePath = loadingImagePath;
    this.errorImagePath = errorImagePath;
  }

}

Finally, when the image is not loaded, our error image will be displayed.

When the image loads and an error happens

But what about developer experience?

Adding the loading and error image paths becomes a tedious job when you have more than one place where you need to set those up. We would like to keep that functionality if we ever need to override default values on another page. Let's set up our pipe's container module in a way that we can set default values in the app's root module.

First, we create the injectors:

import { InjectionToken } from '@angular/core';

export const THIS_DOT_LOADING_IMAGE_PATH = new InjectionToken<string>('THIS_DOT_LOADING_IMAGE_PATH');
export const THIS_DOT_ERROR_IMAGE_PATH = new InjectionToken<string>('THIS_DOT_ERROR_IMAGE_PATH');

Then, we create our forRoot method:

@NgModule({
  imports: [CommonModule],
  declarations: [UseHttpImageSourcePipe],
  exports: [UseHttpImageSourcePipe],
})
export class UseHttpImageSourcePipeModule {
  static forRoot(
    config: { loadingImagePath?: string; errorImagePath?: string } = {}
  ): ModuleWithProviders<UseHttpImageSourcePipeModule> {
    return {
      ngModule: UseHttpImageSourcePipeModule,
      providers: [
        // set up the providers
        {
          provide: THIS_DOT_LOADING_IMAGE_PATH,
          useValue: config.loadingImagePath || null,
        },
        {
          provide: THIS_DOT_ERROR_IMAGE_PATH,
          useValue: config.errorImagePath || null,
        },
      ],
    };
  }
}

And in our app.module.ts file:

@NgModule({
  // ...
  imports: [
    BrowserModule,
    HttpClientModule,
    UseHttpImageSourcePipeModule.forRoot({
      loadingImagePath: 'assets/images/loading.png',
      errorImagePath: 'assets/images/error.png',
    }),
    // ...
  ],
  // ...
})
export class AppModule {}

With this setup, we can inject the THIS_DOT_LOADING_IMAGE_PATH and the THIS_DOT_ERROR_IMAGE_PATH in our pipe, and update the logic so it defaults to that.

@Pipe({
  name: 'useHttpImgSrc',
  pure: false
})
export class UseHttpImageSourcePipe implements PipeTransform, OnDestroy {
  // ...
  constructor(
    private httpClient: HttpClient,
    private domSanitizer: DomSanitizer,
    private cdr: ChangeDetectorRef,
    @Inject(THIS_DOT_LOADING_IMAGE_PATH) private defaultLoadingImagePath: string,
    @Inject(THIS_DOT_ERROR_IMAGE_PATH) private defaultErrorImagePath: string
  ) {
  }

  transform(
    imagePath: string,
    loadingImagePath?: string,
    errorImagePath?: string
  ): string | SafeUrl {
    this.setLoadingAndErrorImagePaths(loadingImagePath, errorImagePath);
    // ...
  }

  // ...

  private setLoadingAndErrorImagePaths(
    loadingImagePath: string = this.defaultLoadingImagePath,
    errorImagePath: string = this.defaultErrorImagePath
  ): void {
    if (this.loadingImagePath && this.errorImagePath) {
      return;
    }
    this.loadingImagePath = loadingImagePath;
    this.errorImagePath = errorImagePath;
  }
}

With that, we can update our templates.

  <img width="200px" [src]="'assets/images/success.png' | useHttpImgSrc" />
  // ...
  <img width="200px" [src]="'assets/images/notfound.png' | useHttpImgSrc" />

And everything works as before. We can still override the loading and error images by passing properties to the pipe, but with Angular's dependency injection, we made it simpler to use default values. Using the injection tokens we can also override the values on component level if we ever need that.

This Dot is a consultancy dedicated to guiding companies through their modernization and digital transformation journeys. Specializing in replatforming, modernizing, and launching new initiatives, we stand out by taking true ownership of your engineering projects.

We love helping teams with projects that have missed their deadlines or helping keep your strategic digital initiatives on course. Check out our case studies and our clients that trust us with their engineering.

You might also like

Overview of the New Signal APIs in Angular cover image

Overview of the New Signal APIs in Angular

Overview of the New Signal APIs in Angular Google's Minko Gechev and Jeremy Elbourn announced many exciting things at NG Conf 2024. Among them is the addition of several new signal-based APIs. They are already in developer preview, so we can play around with them. Let's dig into it, starting with signal-based inputs and the new matching outputs API. Signal Based Inputs Discussions about signal-based inputs have been taking place in the Angular community for some time now, and they are finally here. Until now, you used the @Input() decorator to define inputs. This is what you'd have to write in your component to declare an optional and a required input: ` With the new signal-based inputs, you can write much less boilerplate code. Here is how you can define the same inputs using the new syntax: ` It's not only less boilerplate, but because the values are signals, you can also use them directly in computed signals and effects. That, effectively, means you get to avoid computing combined values in ngOnChanges or using setters for your inputs to be able to compute derived values. In addition, input signals are read-only. The New Output I intentionally avoid calling them signal-based outputs because they are not. They still work the same way as the old outputs. The Angular team has just introduced a new output API that is more consistent with the latest inputs API and allows you to write less boilerplate, similar to the new input API. Here is how you would define an output until now: ` Here is how you can define the same output using the new syntax: ` The thing I like about the new output API is that sometimes it happens to me that I forget to instantiate the EventEmitter because I do this instead: ` You won't forget to instantiate the output with the new syntax because the output function does it for you. Signal Queries I am sure most readers know the @ViewChild, @ViewChildren, @ContentChild, and @ContentChildren decorators very well and have experienced the pain of triggering the infamous ExpressionChangedAfterItHasBeenCheckedError or having the values unavailable when needed. Here is a refresher on how you would use these decorators until now: ` With the new signal queries, similar to the new input API, the values are signals, and you can use them directly in computed signals and effects. You can define the same queries using the new syntax: ` Jeremy Elbourn mentioned that the new signal queries have better type inference and are more consistent with the new input and output APIs. He also showcased a brand new feature not available with the old queries. You can now define a query as required, and the Angular compiler will throw an error if the query has no result, guaranteeing that the value won't be undefined. Here is how you can define a required query: ` Model Inputs Jeremy and Minko announced the last new feature is the model inputs. The name is vague, but the feature is cool—it simplifies the definition of two-way bindings. Until now, to achieve two-way binding, you would have to define an input and an output following a given naming convention. @Input and @Output had to be defined with the same name (followed by "Change" in the case of the output). Then, you could use the template's [()] syntax. ` ` That way, you could keep the value in sync between the parent and the child component. With the new model inputs, you can define a two-way binding with a single line of code. Here is how you can define the same two-way binding using the new syntax: ` The html template stays the same: ` The model function returns a writable signal that can be updated directly. The value will be propagated back to any two-way bindings. Conclusion The new signal-based APIs are a great addition to Angular. They allow you to write less boilerplate code and make your components more reactive. The new APIs are already in developer preview, so you can start playing around with them today. I look forward to seeing how the community will adopt these new features and what other exciting things the Angular team has in store for us, such as zoneless apps by default....

Using HttpClient in Modern Angular Applications cover image

Using HttpClient in Modern Angular Applications

Introduction With all the wonderful treats that the Angular team has given us during the recent "renaissance" era, many new developers are joining in on the fun. And one of the challenges they'll face at some point is how to call an API from your Angular application properly. Unfortunately, while searching for a guide on how to do this, they might stumble upon a lot of outdated information. Hence, this article should serve as a reliable guide on how to use the HttpClient in Angular >= 17. The Setup To make an HTTP request in Angular, you can take advantage of the HttpClient provided by the @angular/common/http package. To use it, you'll need to provide it. Here's how you can do that for the whole application using the bootstrapApplication function from @angular/platform-browser: ` With that, you should be good to go. You can now inject the HttpClient into any service or component in your application. Using HttpClient in Services Let's take a common example: You have a database object, say a Movie, and you want to implement CRUD operations on it. Typically, you'll want to create a service that provides methods for these operations. Let's call this service MovieService and create a skeleton for it with a method for getting all movies. ` Implementing the Method using HttpClient Let's assume we have a GraphQL API for our movies. We can implement our getAllMovies using HttpClient to make a request to fetch all movies. First, we will need to define a new type to represent the response from the API. This is especially important when you are using GraphQL, which may return a specific structure, such as: ` When working with a real API, you'll likely use some code generator to generate the types for the response from the GraphQL schema. But for the sake of this example, we'll create an interface to represent the response manually: ` Now, we can implement the getAllMovies method using HttpClient: ` > Note: The post method is used here because we are sending a request body. If you are making a GET request (e.g. to a REST API), you can use the get method instead. The getAllMovies method returns an Observable of MoviesListResponse. In this example, I have typed it explicitly to make it obvious at first glance, but you could also omit the type annotation, and TypeScript should infer it. > Note: While I'm excited about signals as much as the next guy, making HTTP requests is one of the typical async operations for which RxJS Observables are a perfect fit, making a great argument for RxJS still having a solid place in Angular alongside signals. Using the Service in a Component Now that we have our MovieService set up, we can use it as a component to fetch and display all movies. But first, let's create a Movie interface to represent the structure of a movie. Trust me, this will prevent many potential headaches down the line. Although using any at the beginning and implementing the types later is a valid approach in some cases, using data fetched from an API without validating the type will inevitably lead to bugs that are difficult to solve. ` Now, we can start implementing our standalone MoviesComponent: ` In this component, we are calling the getAllMovies method from the MovieService in the constructor to fetch all movies and assign them to the movies property which we will use to display the movies in the template. ` In this case, placing our code inside the constructor is safe because it doesn’t depend on any @Input(). If it were, our code would fail because the inputs aren’t initialized at time of instantiation. That's why it is sometimes recommended to place logic in ngOnInit instead. You could also put this call in an arbitrary method that is called e.g. on a button click. Another way to handle the subscription is to use the async pipe in the template. This way, Angular will automatically subscribe and unsubscribe from the observable for you and you won't have to assign the response to a property in the component. Due to the structure of the returned data, however, we'll need to use the RxJS map operator to extract the movies from the response. ` > Note using the pipe method to chain the map operator to the observable returned by getAllMovies. This is a common pattern when working with RxJS observables introduced in RxJs 5.5. If you see code that uses the map operator directly on the observable and wonder why it isn't working for you, it's likely using an older version of RxJS. Now, we can simply apply the async pipe in the template to subscribe to the observable and display the movies: ` Conclusion HttpClient in Angular is pretty straightforward, but with all the changes to RxJS and Angular in the past few years, it can pose a significant challenge if you're not super-experienced with those technologies and stumble upon outdated resources. Following this article, you should hopefully be able to implement your service using HttpClient to make requests to an API in your modern Angular application and avoid the struggle of copying outdated code snippets....

Introducing the express-typeorm-postgres Starter Kit cover image

Introducing the express-typeorm-postgres Starter Kit

Here at This Dot, we've been working with ExpressJS APIs for a while, and we've created a starter.dev kit for ExpressJS that you can use to scaffold your next backend project. The starter kit uses many well-known npm packages, such as TypeORM or BullMQ and integrates with databases such as PostgreSQL and Redis. Kit contents The express-typeorm-postgres starter kit provides you with infrastructure for development, and integrations with these infrastructures. It comes with a working Redis instance for caching and a second Redis instance for queues. It also starts up a Postgres instance for you, which you can seed with TypeORM. The infrastructure runs on docker using docker-compose. The generated project comes with prettier and eslint set-up, so you only need to spend time on configuration if you want to tweak or change the existing rules. Unit testing is set up using Jest, and there are some example tests provided with the example controllers. How to initialise API development usually requires more infrastructure than front-end development. Before you start, please make sure you have docker and docker-compose installed on your machine. To initialize a project with the express-typeorm-postgres kit, run the following: 1. Run npx @this-dot/create-starter to run the scaffolding tool 2. Select the Express.js, TypeORM, and PostgreSQL kit from the CLI library options 3. Name your project 4. cd into your project directory, and install dependencies using the tool of your choice (npm, yarn or pnpm) 5. copy the contents of the .env.example file into a .env file With this setup, you have a working starter kit that you can modify to your needs. TypeORM and Database When we started developing the kit, we decided to use PostgreSQL as the database, because it is a powerful, open-source object-relational database system that is widely used for storing and manipulating data. It has a strong reputation for reliability, performance, and feature richness, making it a great choice for a wide range of applications. It can also handle high levels of concurrency and large amounts of data, and supports complex queries and data types. Postgres is also highly extensible because it allows developers to add custom functions and data types to the database. It has a large and active community of developers and users who contribute to its ongoing development and support. The kit uses TypeORM to connect to the database instance. We chose TypeORM because it makes it easy to manage database connections and perform common database operations, such as querying, inserting, updating and deleting data. It supports TypeScript and a wide range of databases, such as PostgreSQL, MySQL, SQLite and MongoDB, therefore if you want to be able to switch between databases, it makes it easier. TypeORM also includes features such as database migrations, which help manage changes to database schema over time, and an entity model that allows you to define your database schema using classes and decorators. Overall, TypeORM is a useful tool for improving the efficiency and reliability of database-related code, and it can be a valuable addition to any TypeScript or JavaScript project that needs to interact with a database. To seed an initial set of data into your database, run the following commands: 1. npm run infrastructure:start - this starts up the database instance 2. npm run db:seed - this leverages TypeORM to seed the database. The seed command runs the src/db/run-seeders.ts file, where you can introduce your seeders for your own needs. The kit uses TypeORM-extension for seeding. Please refer to the src/db/seeding/technology-seeder.ts file for an example. Caching Storing response data in caches allows subsequent requests for the same data to be served more quickly. This can improve the performance and user experience of an API by reducing the amount of time it takes to retrieve data from the server. It can reduce the load on the database or mitigate rate limiting on third-party APIs called from your back-end. It also improves the reliability of an application by providing a fallback mechanism in case the database or the server is unavailable or slow to respond. There is a Redis instance set up in the kit to be used for caching data. Under the hood, we use the cachified library to store cached data in Redis. The kit has a useCache method exported from src/cache/cache.ts, which requires a key and a callback function to be called to fetch the data. ` When you need to invalidate cache entries, you can use the clearCacheEntry method by supplying a key string to it. It will remove the cached data from Redis, and the next request that fetches from the database will cache the new values. ` Under the src/modules/technology folder, you can see a complete example of a basic CRUD REST endpoint with caching enabled. Feel free to use those handlers as examples for your development needs. Queue A message queue allows different parts of an application, or different applications, to communicate with each other asynchronously by sending and receiving messages. This can be useful in a variety of situations, such as when one part of the application needs to perform a task that could take a long time, or when different parts of the application need to be decoupled from each other for flexibility and scalability. We chose BullMQ because it is a fast, reliable, and feature-rich message queue system. It is built on top of the popular Redis in-memory data store, which makes it very performant and scalable. It has support for parallel processing, rate limiting, retries, and a variety of other features that make it well-suited for a wide range of use cases. BullMQ has a straightforward API and good documentation. The kit has a second Redis instance set up to be used with BullMQ, and there is a queue set up out of the box, so resource-intensive tasks can be offloaded to a background process. The src/queue/ folder contains all the configuration and setup steps for the queue. Both the queue and its worker is set up in the queue.ts file. The job-processor.ts file contains the function that will process the data. To run int in a separate thread, we must pass the path to this file into the worker: ` When to use this kit This kit is most optimal when you: - want to build back-end services that can be consumed by other applications and services using ExpressJS - need a flexible and scalable way to build server-side applications - need to deal with CPU-intense operations on the server and you need a messaging queue - need to build an API with relational data - would like to just jump right into API development with ExpressJS using TypeORM and Postgres Conclusion The express-typeorm-postgres starter kit can help you kickstart your development by providing you with a working preset. It has testing configured, and it comes with a complete infrastructure orchestrated by docker-compose....

The simplicity of deploying an MCP server on Vercel cover image

The simplicity of deploying an MCP server on Vercel

The current Model Context Protocol (MCP) spec is shifting developers toward lightweight, stateless servers that serve as tool providers for LLM agents. These MCP servers communicate over HTTP, with OAuth handled clientside. Vercel’s infrastructure makes it easy to iterate quickly and ship agentic AI tools without overhead. Example of Lightweight MCP Server Design At This Dot Labs, we built an MCP server that leverages the DocuSign Navigator API. The tools, like `get_agreements`, make a request to the DocuSign API to fetch data and then respond in an LLM-friendly way. ` Before the MCP can request anything, it needs to guide the client on how to kick off OAuth. This involves providing some MCP spec metadata API endpoints that include necessary information about where to obtain authorization tokens and what resources it can access. By understanding these details, the client can seamlessly initiate the OAuth process, ensuring secure and efficient data access. The Oauth flow begins when the user's LLM client makes a request without a valid auth token. In this case they’ll get a 401 response from our server with a WWW-Authenticate header, and then the client will leverage the metadata we exposed to discover the authorization server. Next, the OAuth flow kicks off directly with Docusign as directed by the metadata. Once the client has the token, it passes it in the Authorization header for tool requests to the API. ` This minimal set of API routes enables me to fetch Docusign Navigator data using natural language in my agent chat interface. Deployment Options I deployed this MCP server two different ways: as a Fastify backend and then by Vercel functions. Seeing how simple my Fastify MCP server was, and not really having a plan for deployment yet, I was eager to rewrite it for Vercel. The case for Vercel: * My own familiarity with Next.js API deployment * Fit for architecture * The extremely simple deployment process * Deploy previews (the eternal Vercel customer conversion feature, IMO) Previews of unfamiliar territory Did you know that the MCP spec doesn’t “just work” for use as ChatGPT tooling? Neither did I, and I had to experiment to prove out requirements that I was unfamiliar with. Part of moving fast for me was just deploying Vercel previews right out of the CLI so I could test my API as a Connector in ChatGPT. This was a great workflow for me, and invaluable for the team in code review. Stuff I’m Not Worried About Vercel’s mcp-handler package made setup effortless by abstracting away some of the complexity of implementing the MCP server. It gives you a drop-in way to define tools, setup https-streaming, and handle Oauth. By building on Vercel’s ecosystem, I can focus entirely on shipping my product without worrying about deployment, scaling, or server management. Everything just works. ` A Brief Case for MCP on Next.js Building an API without Next.js on Vercel is straightforward. Though, I’d be happy deploying this as a Next.js app, with the frontend features serving as the documentation, or the tools being a part of your website's agentic capabilities. Overall, this lowers the barrier to building any MCP you want for yourself, and I think that’s cool. Conclusion I'll avoid quoting Vercel documentation in this post. AI tooling is a critical component of this natural language UI, and we just want to ship. I declare Vercel is excellent for stateless MCP servers served over http....

Let's innovate together!

We're ready to be your trusted technical partners in your digital innovation journey.

Whether it's modernization or custom software solutions, our team of experts can guide you through best practices and how to build scalable, performant software that lasts.

Prefer email? hi@thisdot.co