Catalin Ciubotaru avatar

Server Status Codes with Angular

Apparently, the browser and the server are not that similar.

The Problem

Now that you have managed to setup Angular Universal for your project, to take advantage of that sweet and fast server-side rendering, you put your feet up, and you think to yourself:

I’m done with this. I can focus on the client, the fun part!

Some 2 weeks later

Someone from the Google headquarters gave you a tip. If your server returns 404 when trying to access a movie/car/book that does not exist, your SEO will get better because Google will stop crawling those bad URLs (that exist on the internet, somewhere).

Also, now that you released v2 of your website, you want to redirect from movies?category=drama to movies/drama , but you also don’t wanna lose the street cred those pages already have with Google. Same good friend told you that if you do a 301 Permanent Redirect, Google will get smarter and re-allocate that already earned credit to the new page.

That sounds awesome!

you say to yourself.

But wait, how do I use different SERVER status codes within my Angular code? I don’t wanna make my server suddenly aware of all the logic that goes on in my website. Hmm…

The Solution

Well, it’s not that difficult. Apparently, you can inject the Response from express and use that to your liking.

First part of the solution: the StatusCodeResponseService:


import { Inject, Injectable, Optional } from '@angular/core';
import { RESPONSE } from '@nguniversal/express-engine/tokens';
import { Response } from 'express';

@Injectable()
export class StatusCodeResponseService {
  private response: Response;

  constructor(
    @Optional()
    @Inject(RESPONSE)
    response: any
  ) {
    this.response = response;
  }

  /**
   * Set a status code on the response for given status code and message.
   *
   * @param {number} code
   * @param {string} message
   */
  setStatus(code: number, message?: string) {
    if (!this.response) {
      return;
    }

    this.response.statusCode = code;

    if (message) {
      this.response.statusMessage = message;
    }
  }

  /**
   * Set a 404 Not Found status code on the response
   *
   * @param {string} message
   */
  setNotFound(message = 'Not Found') {
    if (!this.response) {
      return;
    }

    this.response.statusCode = 404;
    this.response.statusMessage = message;
  }

  setPermanentRedirect(newUrl: string) {
    if (!this.response) {
      return;
    }

    this.response.redirect(301, newUrl);
    this.response.finished = true;
    this.response.end();
  }

  /**
   * Set a 500 Internal Server Error status code on the response
   *
   * @param {string} message
   */
  setInternalServerError(message = 'Internal Server Error') {
    if (!this.response) {
      return;
    }

    this.response.statusCode = 500;
    this.response.statusMessage = message;
  }
}
  

This is the bread and butter of our setup. Some key points here:

  • The response is an @OPTIONAL injected parameter
  • We can use the statusCode, statusMessage, finished and end() on the response
  • The RESPONSE is an express-engine/token. So make sure you are using the correct import

Now, in order for this to work, we have to make sure we provide this service in our AppModule, or CoreModule, or whatever your providing strategy is. Something along these lines maybe:


import { NgModule } from '@angular/core';
import { StatusCodeResponseService } from './status-code-response.service';

@NgModule({
  providers: [StatusCodeResponseService],
})
export class CoreModule {}
  

How to use it?

Now, how do you use it? Well, however you want. It can set 404s, it can set 301s, it can set 500s, it can bark... ow wait, it cannot do that…YET!


import { Injectable, Optional } from '@angular/core';
import { Observable } from 'rxjs';
import { StatusCodeResponseService } from './status-code-response.service';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { environment } from '@env/environment';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class MovieService {
  constructor(@Optional() private statusCodeResponseService?: StatusCodeResponseService,
                          private httpClient: HttpClient) {}

  getMovieById(id: string): Observable<Movie> {
    return this.httpClient.get<Movie>(environment.moviesBaseUrl).pipe(
      catchError((error) => {
        if (error instanceof HttpErrorResponse && error.statusCode === 404) {
          this.statusCodeResponseService?.setNotFound('Movie does not exist...yet!');
          return EMPTY;
        }

        return throwError(error);
      })
    );
  }
}
  

This is one very simple example of how to use this service for a 404 scenario. This will short-circuit everything and will return a simple 404 to the browser with the message Movie does not exist...yet!.

This is not the nicest solution. Ideally, you would wanna have SomeComponent listen to this as well, maybe show a nice UI in case a 404 is issued, but you get the point. The world is your oyster!

Want more?

If you want to see more advanced use cases for this, please let me know, so I can do a follow-up with some nice UI handling, and stuff like that.

Also, if you have questions, you know where to find me… On the internet!

Be kind to each other! 🧡

Over and out

My Twitter avatar
Catalin Ciubotaru 🚀

Got a new blog post 📝, fresh out of the oven: "Angular life: Server Status Codes with Angular Universal" #Angular #AngularUniversal

Jul 27, 2021
42 people are talking about this

Wanna read more?