Skip to content

Realtime App With Angular and Firestore (AngularFire)

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.

angularfire

In this tutorial, I'm going to show you how to use Firestore, which is a realtime database from Firebase with Angular. We are going to make use of the basic CRUD opeartions as well as AGM (Angular Google Maps).

Overview

This application will help you to have a realtime map with a data table showing your favorite places that you have visted or want to visit, so you can share this info with your friends. Screen Shot 2019-12-10 at 1.43.27 AM

Firebase setup

  1. Go to Firebase, Sign in with your google account
  2. Go to the console
  3. Click on Add project
  4. Give your project a name
  5. Disable __ Google Analytics for this project__
  6. Click on Create Project
  7. You will see a screen like this. Click on the web icon Screen Shot 2019-12-10 at 1.49.52 AM
  8. Register your app
  9. Copy the firebaseConfig object, and paste it somewhere secure, we will use it later on
  10. Click on Continue to Console
  11. Click on the big orange box that says Cloud Firestore
  12. Then, on the top, you will see a button that says Create database
  13. A modal will show up. Click on radio button__ Start in test mode__
  14. Click Next
  15. Select your Firestore location
  16. Click Done

Angular

Time to work on our Angular application.

  1. Create a new angular app

Note: Make sure to select SCSS as you CSS compiler.

ng new thisdot-tutorial
  1. Create a component for our form
ng g c places-form
  1. Create a component for our list of favorite places
ng g c places-list
  1. Create a component for our map with our favorite places
ng g c places-map
  1. Create a new service as follow:
ng g s places

Now, we are going to install all the packages needed, so we don't have to go back to our app.module file and deal with them later.

  1. Install firebase
npm i --save firebase @angular/fire
  1. Install Angular Google Maps (AGM)
npm i --save @agm/core
  1. Go to your app.module, and add the following modules as follows
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

/*External Modules */
import { ReactiveFormsModule } from '@angular/forms';
import { AngularFireModule } from '@angular/fire';
import { AngularFirestoreModule } from '@angular/fire/firestore';
import { AgmCoreModule } from '@agm/core';

/*App components */
import { AppComponent } from './app.component';
import { PlacesListComponent } from './places-list/places-list.component';
import { PlacesFormComponent } from './places-form/places-form.component';
import { PlacesMapComponent } from './places-map/places-map.component';

import { environment } from 'src/environments/environment';

@NgModule({
  declarations: [
    AppComponent,
    PlacesListComponent,
    PlacesFormComponent,
    PlacesMapComponent
  ],
  imports: [
    BrowserModule,
    ReactiveFormsModule,
    AngularFireModule.initializeApp(environment.firebaseConfig),
    AngularFirestoreModule,
    AgmCoreModule.forRoot({
      apiKey: 'GOOGLE MAPS API KEY'
    })
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}
  1. Generate a Google API Key Also, you'll need to make sure you have Maps JavaScript API service enabled in the Google Service Library

  2. Copy the API key, and paste it in the AgmCoreModule inside of the app.module.ts

  3. As you can see in the app.module.ts file, we have the following line:

AngularFireModule.initializeApp(environment.firebaseConfig),

This line is using the enviroment variable.

Go to the enviroment.ts file, and make sure your code looks like the following:

export const environment = {
  production: false,
  firebaseConfig: {
    apiKey: 'XXXXXXXX',
    authDomain: 'XXXXXXXX',
    databaseURL: 'XXXXXXXX',
    projectId: 'XXXXXXXX',
    storageBucket: 'XXXXXXXX',
    messagingSenderId: 'XXXXXXXX',
    appId: 'XXXXXXXX',
    measurementId: 'XXXXXXXX'
  }
}

Note: Copy/paste the credentials we copied from the firebase console earlier in the tutorial.

  1. Inside of your index.html file, add Semantic UI CDN to give some quick styling to our app
<link
  rel="stylesheet"
  href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css"
  integrity="sha256-9mbkOfVho3ZPXfM7W8sV2SndrGDuh7wuyLjtsWeTI1Q="
  crossorigin="anonymous"
/>
  1. Add the fontawesome CDN inside the index.html file

Get your own CDN link here

  1. Create a new model file with the name place.model.ts

  2. Copy/paste the following code inside of the place model

export interface Place {
  name: string;
  lat: number;
  long: number;
  id: string;
  visited: false;
}
  1. Go to your places-form.components.ts file. We are going to create our form as follows:

Note: I'm also injecting via dependency injection, my places service.

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { PlacesService } from '../places.service';

@Component({
 ....
})

export class PlacesFormComponent implements OnInit {
  form = new FormGroup({
    name: new FormControl(''),
    long: new FormControl(''),
    lat: new FormControl('')
  });
  constructor(private placesService: PlacesService) {}

  ngOnInit() {}

  onSubmit() {
    const place = this.form.value;
    this.placesService.addPlace({ ...place, visited: false });
    this.form.reset();
  }
}
  1. Inside of your places-form.component.html, copy/paste the code that contains the form.
 <div class="form-fav">
      <form class="ui form" [formGroup]="form" (ngSubmit)="onSubmit()">
        <div class="field">
             <input
             type="text"
             name="name"
             placeholder="Place Name"
             formControlName="name"
             />
       </div>
       <div class="field">
            <input
            type="text"
            name="latitude"
            placeholder="Latitude"
           formControlName="lat"
        />
      </div>
      <div class="field">
           <input
           type="text"
           name="Longitude"
           placeholder="Longitude"
           formControlName="long"
           />
      </div>
      <button class="ui inverted violet button" type="submit">
              <i class="fas fa-star"></i> Add to Favorites
      </button>
   </form>
</div>
  1. Now, go to your places-form.component.scss file, and add the following styling.
.form {
  display: flex;
  justify-content: space-evenly;
  border-bottom: 1px solid #222;
  padding: 8px;
  align-items: flex-start;
}
  1. Time to work with our places-list.component.ts file.

__Note: __In this file, we are going to create an Input() that will take the array of places comming from our parent component. Also, notice I'm importing the places service, because we are going to call some actions inside of our service like the delete and edit

import { Component, OnInit, Input } from '@angular/core';
import { PlacesService } from '../places.service';
import { Place } from '../place.model';

@Component({
  selector: 'app-places-list',
  templateUrl: './places-list.component.html',
  styleUrls: ['./places-list.component.scss']
})
export class PlacesListComponent implements OnInit {
  @Input() places: Place[];
  constructor(private placesService: PlacesService) {}

  ngOnInit() {}

  onDelete(id: string) {
    this.placesService.deletePlace(id);
  }

  onUpdate(id: string, visited: boolean) {
    this.placesService.updatePlace(id, visited);
  }
}
  1. In the places-list.component.html file, you are going create the list that displays your favorite places. The last column will be an actions column. This column will allow you to delete or edit your data.
<table class="ui celled table">
  <thead>
    <tr>
      <th><i class="fas fa-map-marker-alt"></i> Place Name</th>
      <th>Latitude</th>
      <th>Longitude</th>
      <th>Actions</th>
    </tr>
  </thead>
  <tbody>
    <tr *ngFor="let p of places">
      <td>{{ p.name }}</td>
      <td>{{ p.lat }}</td>
      <td>{{ p.long }}</td>
      <td class="actions">
        <div class="action remove" (click)="onDelete(p.id)">
          Remove<i class="fas fa-backspace"></i>
        </div>
        <div class="action" (click)="onUpdate(p.id, p.visited)">
          <ng-container *ngIf="p.visited; else notVisited">
            <span class="visited"
              >Visited<i class="fas fa-smile-wink"></i
            ></span>
          </ng-container>
          <ng-template #notVisited>
            <span class="not-visited"
              >Not Visited <i class="fas fa-sad-tear"></i
            ></span>
          </ng-template>
        </div>
      </td>
    </tr>
  </tbody>
</table>
  1. Time to style our table :) Lets make it look at least a little decent. Inside of your places-list.component.scss file, add the following code.
.actions {
  display: flex;
  justify-content: space-around;
  .action {
    display: flex;
    align-items: center;
    cursor: pointer;

    i {
      margin: 2px;
    }
  }

  .remove {
    color: #ff3232;
  }

  .visited {
    color: rgb(32, 218, 141);
  }

  .not-visited {
    color: rgb(252, 214, 0);
  }
}
  1. Time to add the favorites places to our map. Open the places-map.component.ts file, and add the following code.

Note: As you can see, I'm creating an Input() that takes an array of places, which is the data that will populate our markers in the map.

import { Component, OnInit, Input } from '@angular/core';
import { Place } from '../place.model';

@Component({
  selector: 'app-places-map',
  templateUrl: './places-map.component.html',
  styleUrls: ['./places-map.component.scss']
})
export class PlacesMapComponent implements OnInit {
  @Input() places: Place[];

  //This will define the center of the map
  lat = 53.2734;
  long = -7.77832031;

  constructor() {}

  ngOnInit() {}
}
  1. Let's add the agm map component to our places-map.component.html file.

Note: I'm looping throught the array of places to add the markers to our map.

<div class="places">
  <div class="map">
    <agm-map [latitude]="lat" [longitude]="long" [zoom]="4">
      <agm-marker
        *ngFor="let p of places"
        [latitude]="p.lat"
        [longitude]="p.long"
      ></agm-marker>
    </agm-map>
  </div>
</div>
  1. Don't forget to specify the height of your map, or it won't appear. Specify the height inside of the places-map.component.scss file
agm-map {
  height: 100vh;
}
  1. Go to your app.component.ts file. We are going to create a variable that grabs the places stored in our Firestore databse.

Note: As you can see, I'm injecting the places services via dependency injection.

import { Component } from '@angular/core';
import { PlacesService } from './places.service';

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

  constructor(private placesService: PlacesService) {}
  places$ = this.placesService.places$;
}
  1. In this step, we are going to add all of our components to app.component.html for them to be displayed in our application.

Note: Notices I'm passing the places array to the inputs in the places-list, and places-map components. Also notice I'm using the async pipe to handle the subcription and unsubcription of the observable.

<div class="places">
  <app-places-form></app-places-form>
  <div class="columns" *ngIf="places$ | async as places">
    <div class="places-list">
      <app-places-list [places]="places"></app-places-list>
    </div>
    <div class="map">
      <app-places-map [places]="places"></app-places-map>
    </div>
  </div>
</div>
  1. Let's add the main styling to our app to give it the shell structure. Go your app.component.scss file, and add the following code:
.places {
  margin: 18px 18px;
  .columns {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: 1fr;
    grid-template-areas: "places-list map";
  }

  .places-list {
    grid-area: places-list;
  }

  .map {
    grid-area: map;
  }
}

CRUD with Firestore

  1. Go to your places.service.ts, and add the following code:

Note: the places$ field is an observable from firestore, and delete, add, update actions are promises.

import { Injectable } from '@angular/core';
import { AngularFirestore } from '@angular/fire/firestore';
import { Place } from './place.model';
import { map } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class PlacesService {
  constructor(private firestore: AngularFirestore) {}

  firestorePlacesCollection = this.firestore.collection('places');

  //READ
  places$ = this.firestorePlacesCollection.snapshotChanges().pipe(
    map(actions => {
      return actions.map(p => {
        const place = p.payload.doc;
        const id = place.id;
        return { id, ...place.data() } as Place;
      });
    })
  );

  //CREATE
  async addPlace(data: Place): Promise<void> {
    try {
      await this.firestorePlacesCollection.add(data);
    } catch (err) {
      console.log(err);
    }
  }

  //UPDATE
  async updatePlace(id: string, visited: boolean): Promise<void> {
    try {
      await this.firestorePlacesCollection
        .doc(id)
        .set({ visited: !visited }, { merge: true });
    } catch (err) {
      console.log(err);
    }
  }

  //DELETE
  async deletePlace(id: string): Promise<void> {
    try {
      await this.firestorePlacesCollection.doc(id).delete();
    } catch (err) {
      console.log(err);
    }
  }
}

Time to run our app!

  1. In your command line, run
ng serve --o

Note: You might see an error message telling you that something went wrong with the Google Maps API. In this case, you most probably need to enable billing for this project in your Google Cloud Account. No worries, you won't be charged since there is a free tier available.

  1. Create some data, and you should see something like this: Screen Shot 2019-12-10 at 1.43.27 AM

  2. Go back to the Firebase console, and see the Firestore console with the new data created.

Don't forget to follow me on Twitter @devpato, and let me know what you think about this tutorial!

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

Angular 18 Announced: Zoneless Change Detection and More cover image

Angular 18 Announced: Zoneless Change Detection and More

Angular 18 Announced: Zoneless Change Detection and More Angular 18 has officially landed, and yet again, the Angular team has proven that they are listening to the community and are committed to continuing the framework's renaissance. The release polishes existing features, addresses common developer requests, and introduces experimental zoneless change detection. Let's examine the major updates and enhancements that Angular 18 offers. 1. Zoneless Angular One of the most exciting features of Angular 18 is the introduction of experimental support for zoneless change detection. Historically, Zone.js has been responsible for triggering Angular's change detection. However, this approach has downsides, especially regarding performance and debugging complexity. The Angular team has been working towards making Zone.js optional, and Angular 18 marks a significant milestone in this journey. Key Features of Zoneless Angular 1. Hybrid Change Detection: In Angular 18, a new hybrid change detection mode is enabled by default. This mode allows Angular to listen to changes from signals and other notifications regarding changes that occur either inside an Angular zone or not. That effectively means you can write (library) code that works regardless of whether Zone.js is being used, paving the way to fully zoneless apps without compromising backward compatibility. For new applications, Angular enables zone coalescing by default, which removes the number of change detection cycles and improves performance. For existing applications, you can enable zone coalescing by configuring your NgZone provider in bootstrapApplication: ` 2. Experimental API for Zoneless Mode: Angular 18 introduces an experimental API to disable Zone.js entirely. This API allows developers to run applications in a fully zoneless mode, paving the way for improved performance and simpler debugging. The zoneless change detection requires an entirely new scheduler which relies on notifications from the Angular core APIs, such as ChangeDetectorRef.markForCheck (called automatically by the AsyncPipe), ComponentRef.setInput, signal updates, host listener updates, or attaching a view that was marked dirty. 3. Improved Composability and Interoperability: Zoneless change detection enhances composability for micro-frontends and interoperability with other frameworks. It also offers faster initial render and runtime, smaller bundle sizes, more readable stack traces, and simpler debugging. How to Try Zoneless Angular To experiment with zoneless change detection, you can add the provideExperimentalZonelessChangeDetection provider to your application bootstrap: ` After adding the provider, remove Zone.js from your polyfills in angular.json. You can read more about the experimental zoneless change detection in the official documentation. By the way, angular.dev is now considered the official home for Angular developers! 2. Server-side Rendering and Hydration Enhancements A feature I'm particularly excited about is the improvements to Angular's server-side rendering (SSR) and hydration in terms of developer experience and debugging: 1. Enhanced DevTools Support: Angular DevTools now includes overlays and detailed error breakdowns to help visualize and debug hydration issues directly in the browser. This refreshing focus on developer experience shows that the Angular team wants to make the framework more approachable and user-friendly. 2. Hydration Support for Angular Material: All Angular Material components now support client hydration and are no longer skipped, which enhances performance and user experience. 3. Event Replay: Available in developer preview, event replay captures user interactions during SSR and replays them once the application is hydrated, ensuring a seamless user experience before complete hydration. It is powered by the same library as Google Search. 4. i18n Hydration Support: Up to v17, Angular skipped hydration for components with i18n blocks. From v18, hydration support for i18n blocks is in developer preview, allowing developers to use client hydration in internationalized applications. 3. Stable Material Design 3 After introducing experimental support for Material Design 3 in Angular 17, Angular 18 now includes stable support. The key features of Material Design 3 in Angular 18 include: 1. Simplified Theme Styles: Based on CSS variables, the new theming styles offer more granular customization and a flexible API for applying color variants to components. 2. Theming Generation Schematics: Using the Ng CLI, you can generate Material 3 themes. 3. Sass APIs: New Sass APIs allow developers to read colors, typography, and other properties from the Material 3 theme, making it easier to create custom components. How to use Material Design 3 in Angular 18 To use Material Design 3 in Angular 18, you can define a theme in your application's styles.scss file using the mat.defineTheme function: ` Or generate a Material 3 theme using the Ng CLI: ` You can then apply the theme to your application using the mat.theme mixin: ` Head to the official guide for a more detailed guide. You'll also notice they have refreshed the docs with the new themes and further documentation. 4. Signal-Based APIs The path to fully signal-based components includes new signal inputs, model inputs, and signal query APIs. We already wrote about them as they were in developer-preview in v17, but they have been further refined in v18. These APIs offer a type-safe, reactive way to manage state changes and interactions within components: 1. Signal Input API: Signal inputs allow values to be bound from parent to child components. Those values are exposed using a signal and can change during the component's life cycle. 2. Model Input API: Model inputs are a special input type that enables a component to propagate new values back to the parent component. That allows developers to keep the parent component in sync with the child component with two-way binding. 3. Signal Query API: This was a particularly requested feature from the community. The signal query APIs work the same way as ViewChild and ContentChild under the hood, but they return signals, providing more predictable timing and type safety. 5. Fallback Content For ng-content A very requested feature from the community, Angular 18 introduces a new ng-content directive that allows developers to define fallback content when no content is projected into a component. This feature is particularly useful for creating reusable components with default content. Here's an example of using the new ng-content directive. Using the following component ` like this ` will render Howdy World. 6. Other Improvements In addition to the major updates mentioned above, Angular 18 also includes several other improvements and updates: 1. TypeScript 5.4: Angular 18 now supports TypeScript 5.4, which lets you take advantage of new features such as preserved narrowing in closures following last assignments. 2. Global Observable in Angular Forms: Angular 18 introduces a global events observable in Angular forms, which allows you to track all changes around any abstract control and its children, including the touched or dirty in a single observable. Here's an example of how you can use the global observable: ` 3. Stable Deferrable views: Deferrable views are now stable in Angular 18. 4. Stable Control Flow: The built-in control flow is now stable in Angular 18! It is more performant than its predecessor. It also received improved type checking, including guardrails for certain performance-related anti-patterns. 5. Route Redirects as Functions: For added flexibility in managing redirects, Angular v18 now lets you use a function for redirectTo that returns a string, which allows you to create more sophisticated redirection logic based on runtime conditions. For example: ` Conclusion Angular 18 is a significant release that brings many new features, enhancements, and experimental APIs to the Angular ecosystem. The introduction of zoneless change detection, improvements to server-side rendering and hydration, stable Material Design 3 support, signal-based APIs, and fallback content for ng-content are just a few of the highlights of this release. The Angular team has again demonstrated its commitment to improving the framework's developer experience, performance, and flexibility. It also demonstrated a clear vision for Angular's future. If you're curious about what's next, you can check out the Angular roadmap....

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....

Intro To Performance Analytics with Firebase cover image

Intro To Performance Analytics with Firebase

The Firebase suite includes multiple products, about which you can read more here. In this article, however, I'm going to talk about the Performance Monitoring product. I'm going to show you how to use it in an Angular app, but the process for React, VueJS, etc, is very very similar. What Is Performance Monitoring in Firebase? Thanks to this product, you can observe the performance of your app. By using the product, you see the areas for improvement in your code. This product can help you avoid crashes by increasing your code quality. Features Of Performance Monitoring - Customize monitoring for your app - Automatically measure app startup time, HTTP/S network requests, and more - Gain insight into situations where app performance could be improved Let's get started *Note:* I'm assuming you have a Firebase account, and any project there that can be used throughout this article 1) On the left navbar, inside of a Firebase console, you will see Performance. Click it. This is where your data will be populated after 12-24hrs of monitoring. 2) Now, go to project settings: 3) Then, scroll all the way down, and copy/paste the JSON with all your project settings in a safe place. If you don't see those settings as shown on the screenshot, you might need to register a new web-app for the current project (instructions on how to do this are given on the same page in Project settings > General). 4) Navigate to your project directory in the command line, and run: ` 5)Import the Firebase modules in your app.module.ts ` 6) Inside of your app.module.ts, make sure you add the above modules into the imports array as follows: ` 7) Now, in your service, or wherever you are reading the data from Firebase, you can add a trace to trace the time it takes to load the data. ` Note: *places* is the name of my collection inside of Firebase, and *placesQuery* is the name I gave to my trace. You can name it however you want. __Now your app is ready to start getting tracked by Firebase's performance tooling. __ Remember You can always write custom traces whether you are using Angular, React, or plain Vanilla JS. Time to view our App Performance Note: In order to see your app performance, you need to deploy your app and give Firebase approximately 24 hours to collect some data. 7) Go back to Firebase-> Performance Tab, you should see something like this: You will see this dashboard showing some basic data per country, and per enviroment you have used your app. 8) Now, click on View Traces, and click on the enviroment you want to be the traces. You will see a metrics dashboard If you click on View More, you will see more information about that specific metric. Check it out! 9) Now go back to the previous page and click on device tab. Then click on the trace you created to view the performance data. In my case, my custom trace is placeQuery. 10) After clicking on the custom trace, you will see a dashboard that is similar to the one in the picture below. Click on View More. 11) After clicking on view more, you will see some specific traces realted to your custom trace. As you can see, you have the option to see the metrics depending on different factors like the browser, country, etc. 12) You can also set parameters to see when the performance on the page is below the average by setting a threshold. All of these performance metrics will help you understand how your app performs in different conditions to improve the user experience for your customers....

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