Angular Universal Rest API Endpoints
Tags: javascript, angular, angularuniversal, webdev
Published: 2022-04-04 04:56:27

If you use NestJS, you know how easy it is to create REST API endpoints. I am personally building an app in SvelteKit that uses them out of necessity. Nust uses them as well. I read an article somewhere talking about "frameworks that support them..." Well, they all support them (okay the main 4), just not out of the box.

- [Nuxt](https://nuxtjs.org/docs/configuration-glossary/configuration-servermiddleware/)
- [SvelteKit](https://kit.svelte.dev/docs/routing#endpoints)
- [NextJS](https://nextjs.org/docs/api-routes/introduction)

What you may not know is that Vercel started make this popular in NextJS due to the [File System API](https://vercel.com/docs/file-system-api). Basically, it builds a serverless function for each Rest endpoint in order to minimize the cold start time for each route.

Vercel told me that I shouldn't deploy an Angular Universal App to Vercel due to the [AWS Lambda 50MB](https://vercel.com/docs/concepts/limits/overview#serverless-function-size) limit. Well, it is actually 250 MB, unzipped.

So, I created a way to [deploy to Vercel](https://dev.to/jdgamble555/how-to-deploy-angular-universal-to-vercel-31d0) anyway. I'm a rebel. 

This post does not take into account serverless functions, but it would be easy to do so in Vercel. Just add a new file to the `api` folder.

That being said, let's begin.

## handler.ts

Create a `handler.ts` file in your root directory. Here are my example contents. This handles all routes, but you could easily separate them out into different files.

```typescript
export const handler = (req: any, res: any) => {
  const func = req.params[0];
  let r = 'wrong endpoint';

  if (func === 'me') {
    r = me();
  } else if (func === 'you') {
    r = you();
  }
  res.status(200).json({ r });
};

const me = () => {
  return 'some data from "me" endpoint';
};

const you = () => {
  return 'some data from "you" endpoint';
};
```

## server.ts

Look for this commented out line:

```typescript
// Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { });
// Serve static files from /browser
```

Change it to this:

```typescript
// remember to import handler at top of page
import { handler } from 'handler';
...

// Example Express Rest API endpoints
server.get('/api/**', handler);
```

And that's it for the backend!

As easy as that part was, I still believe Angular Universal can simplify these things.

## app.component.ts

```typescript
import { DOCUMENT, isPlatformServer } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { 
 Component, 
 Inject, 
 Optional, 
 PLATFORM_ID
} from '@angular/core';
import { firstValueFrom } from 'rxjs';
import { REQUEST } from '@nguniversal/express-engine/tokens';

declare const Zone: any;

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {

  title = 'angular-test';

  data!: string;
  baseURL!: string;
  isServer: Boolean;

  constructor(
    @Inject(PLATFORM_ID) platformId: Object,
    @Optional() @Inject(REQUEST) private request: any,
    @Inject(DOCUMENT) private document: Document,
    private http: HttpClient
  ) {
    this.isServer = isPlatformServer(platformId);

    // get base url
    if (this.isServer) {
      this.baseURL = this.request.headers.referer;
    } else {
      this.baseURL = this.document.location.origin + '/';
    }

    // grab data
    this.getData().then((data) => this.data = data.r);
  }

  async getData(): Promise<any> {
    return await firstValueFrom(
      this.http.get(this.baseURL + 'api/me', {
        headers: {
          'Content-Type': 'application/json',
        },
        responseType: 'json'
      })
    );
  };
}
```
## So, there are a few key concepts here. 

1. Use `HttpClient` to get the data. Angular returns this as an observable, so make it a promise. Don't forget to add `HttpClientModule` to the imports of `app.module.ts`.
2. The server does not know what your base URL is. If you don't care about testing with `npm run dev:ssr`, you don't need to worry about it, and just use the full url. However, if you want it to work locally and in production, you need to get the correct baseURL. It is passed to the headers in the request object, so we just get it from that object on the server. In the browser, we get it from origin. There are many ways to do this, but I went with the `DOCUMENT` route.
3. Add `<h1>{{ data }}</h1>` to your `app.component.html` file.

## Example

So, here is this masterpiece at hand:

https://angular-endpoint-test.vercel.app/

and of course the [Github](https://github.com/jdgamble555/angular-endpoint-test).

## Don't Fetch Twice

There is one more step you should do, which I left out for brevity. Angular is fetching your REST API endpoint two times: once from the server, and once from the browser. This means you get one more read than necessary.

Now the code above does fetch twice, but you could fetch once on the server, populate the DOM, save the data as a JSON string, and re-apply the data to the Incremental DOM.

I already wrote an article on this about [passing state from server to browser](https://dev.to/jdgamble555/how-to-properly-fetch-data-in-angular-universal-1n6n) in Angular. 

So, this should be implemented as well.

Happy Angular Universaling,

J

**UPDATE: 4/5/22** - I updated my [Github](https://github.com/jdgamble555/angular-endpoint-test) and [deployment](https://angular-endpoint-test.vercel.app/) to transfer state correctly so that it only fetches once.