Skip to content

Getting Started with Angular: A Mobile Developer’s Approach

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.

At many points in your career, you will find yourself picking up a new framework, platform, language, or (most likely) some combination of all three. In my case, I recently found myself looking for a change of pace, and ended up switching from working in native iOS as a Mobile Engineer to the world of JavaScript as a Front-End Engineer. Specifically, I started out with Angular.

No matter how you slice it, learning something brand new can feel daunting — regardless of whether it is mid-career or otherwise. Luckily, Angular is extremely simple and quick to get set up . . . but getting an environment and project set up really is only a small part of the path to learning a platform, isn’t it? As I challenged myself to attempt to learn Angular as quickly as possible, I aimed to first break apart the approach by understanding:

  • What Angular is
  • What Angular's goal is
  • What the fundamental pieces of Angular are

So, before we jump straight into some code, let's grasp these concepts first.

What is Angular?

First of all, Angular is not AngularJS. This was slightly confusing for me at first, and I made the mistake of mixing them up initially. Oops. At a macro level, Angular is a modern JavaScript framework that’s built on TypeScript, giving it access to static types, classes, and other elements provided by ES6. This allows code to organize and structure properly so it can be Object-Oriented, and allow for long-term maintenance at any scale, which will be important for the next fundamental concepts.

What does Angular aim to achieve?

Angular provides a platform to make single-page applications. I think it's important to point out the usage of the word platform, as Angular comes with quite a bit more to help us scale applications to any point we need. It ships with many integrated libraries (more on that in a bit), integration with numerous third-party libraries, and, maybe most importantly, the Angular CLI (command line interface), which gives us access to build, serve, test, and even ship our application. "Application" is also a very important distinction- that is, Angular (like many modern frameworks) aims to provide the tools to create responsive, smooth user experiences.

What are the essential building blocks of Angular?

To understand Angular, you have to understand what a Component is. But first, it may be easier to first visualize what makes an application: navbars, buttons, tables, table cells, and every other separately distinct building block you can visually see. In Angular, every single one of these is a Component, and everything else that exists is in service of these Components. For me, this is where it really started to click, since in iOS, the same fundamental principle exists: every single object on the screen must conform to UIView, including any View Controller. Angular, of course, is no different — the root view of the app itself is also a Component.

Templates define the appearance of the Component via HTML. Angular extends the functionality of templates quite a bit through direct text interpolation, directives, and property/event binding. These, combined with CSS, can achieve essentially anything you need it to without having to entangle your Component's business logic with the presentation of said Component.

The final core piece of the Angular paradigm is dependency injection. Essentially, dependency injection helps keep your Components extremely light, modular, and maintainable. So, say you have a table that needs data from a request sent to an API endpoint. You could just have the inner workings of a server fetch right there all in the component, but you likely have multiple places all over your application where you'll be making API requests, and have to duplicate that code. Instead, Angular lets you define a Service object that can handle that API request, and return the data directly to your component. You don't even need to instantiate the Service since Angular will just automatically do that for you once you inject the dependency to keep things even cleaner.

Dependency injection, services, routing, etc. can feel daunting, but I think as long as you view everything else that isn't a Component solely existing to work in favor of Components, things become a lot clearer and you can start building apps the Angular way.

Speaking of, let's do a bit of project setup, take a look at the structure, build it, and add a component.

Prerequisties

Now that we know what Angular is, we can finally start our first project. A couple things we will need first:

Creating and running your first project

With Node and NPM installed, we can install the Angular CLI with the following command:

npm install -g @angular/cli

Then we create our first project with:

ng new my-first-app

Note: The above command will have a couple prompts for you. Answer yes to add Angular routing, and the style sheet format is up to you (I chose SCSS). You can just press enter on both prompts for the default setup as well.

And finally, building and serving our new app locally:

cd my-first-app
ng serve

Open http://localhost:4200/ on your favorite browser to see what we just made. One note about ng serve is its ability to pick up code changes and update live — no waiting for build times just to see very small changes here. Nice.

angular_getting_started_1

Project Structure

Of course, it's helpful to know what the CLI just built out for us. Looking at our project structure in VS Code, we can see quite a bit of config files set up in the root directory. Of course, these are all important, but for now, let's focus on getting familiar with a few:

  • angular.json — This is the configuration file for CLI, so it knows how to build, serve, and test your app. Should be good to go right out of the box.
  • package.json — The file where the list of all your NPM dependencies live, the package-lock.json specifies the versions for these files (for my iOS friends, this is very similar to the podfile or build.gradle for my Android friends!). Whenever you install a Node package, the tool will automatically update these for you.
  • node_modules — With all the above dependencies installed, the .gitignore should be ignoring this directory by default, but it's good to know everything is there.

Now that we're familiar with the housekeeping of the app, we can dig into the most important part: the code. In the src/app directory, we can see our first module, component, and the files that make it up. Let's ignore those files for now, and create our very first Component.

Creating The First Component

Within your terminal, we'll run the following command:

ng generate component my-first-component

With that, CLI should have made a directory in src/app/my-first-component and the following files:

  1. my-first-component.component.ts — This is the file where the data and logic needed to update your Component's Template live.
  2. my-first-component.component.html — The associated Template for the Component, where the HTML is defined to actually render our Component.
  3. my-first-component.component.scss — The associated style sheet for the Template, where styles scoped only to this Component are defined.
  4. my-first-component.component.spec.ts — The very first unit test for the Component, automatically generated by CLI. How nice!

Within the component.ts file, let's take special note of the following:

@Component({
  selector: 'app-my-first-component',
  templateUrl: './my-first-component.component.html',
  styleUrls: ['./my-first-component.component.scss']
})

Here we can note the Template and style sheet URLs, but of particular note is the selector, in our case called app-my-first-component. This is the custom HTML tag we can now use anywhere our Component is imported in our project. The reason app is prepended to the front of the name is because we defined this Component within the app module, but you can absolutely change the name if you want.

If we look back to the root app directory in src/app (taking special note of the exact same file structure we just created in my-first-component), let's open app.module.ts. The CLI has already created a declaration for our Component in the NgModule decorator, and imported MyFirstComponentComponent automatically for us. Let's actually get our newly created Component on the screen somewhere.

Let's open app.component.html. There's quite a bit of HTML already in here, but we can leave it mostly alone for now. Really, we can throw our Component just about anywhere, but let's search for the following:

<span>Welcome</span>

And replace it with:

<app-my-first-component></app-my-first-component>

Save your changes, and check your browser to see the following:

angular_getting_started_2

Now that we've got it up on the screen, let's do some slight modification to get data from the logic to the Template. First, add the following inside the class definition of my-first-component.component.ts:

title = "Hello World!";

Then, inside my-first-component.component.html, replace the Template definition with the following:

<p>{{ title }}</p>

The double bracket notation tells Angular to interpolate the value within, and dynamically pull the text defined in our class. Save both files, and you should see the following:

angular_getting_started_3

Neat, right?

Conclusion

Whenever someone asks me how easy it is to get started doing native iOS app development, it's easy for me to boast that you can start a project, throw something in Interface Builder, and run it — all within a few minutes' time. Now, I (and hopefully you) can easily say the exact same about Angular.

Of course, this is just a very small taste of what Angular has to offer, but hopefully instead of feeling initially lost in a sea of documentation, the relative simplicity of the platform will seem a lot more clear from the essence of what I described here from my journey of also having to learn Angular.

From here, I think it's valuable to look through the incredible official Angular docs, and I'd recommend running through some of their tutorials as well.

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

SSR Finally a First-Class Citizen in Angular? cover image

SSR Finally a First-Class Citizen in Angular?

I am sure you've already heard about server-side rendering (SSR). Generally, it's a technique used to deliver the HTML content generated by the server to the client. The client-side JavaScript framework then hydrates that HTML content to provide a fully interactive application. SSR provides numerous benefits if used correctly, including improved initial load times, better content indexing by search engines, and improved performance on lower-tier devices. Driven by the need for faster, more efficient web applications, with the rise in popularity of Node.js and, thus, server-side JavaScript, SSR has become a de facto standard in modern web development frameworks. At first, SSR was being provided as a part of meta-frameworks like Next.js, Nuxt, or Analog.js. Then, React introduced React Server Components, which marked a significant milestone in the way we think about front-end development. We think of front-end frameworks as being effectively full-stack, and a purely SPA approach is falling out of favor. However, I'm willing to bet that if you're a developer focused mostly on Angular, you may not have heard as much about SSR as your React counterparts. For a long time, Angular has been mostly focused on the SPA approach, and while we had Angular Universal or Analog.js, it was not as widely adopted. And that makes sense, given that Angular was widely regarded as best suited for complex, interactive applications, such as SaaS products or internal company tools, where SEO and initial load times were not as critical as they are for consumer-facing applications. Nevertheless, we have been blessed with the so-called Angular Renaissance, which has, among other things, brought SSR to the forefront of Angular development with the @angular/ssr package. Let's look at what Angular SSR is, how it works, and how you can get started. @angular/ssr The @angular/ssr package is not new; it was written from scratch. It's still Angular Universal. However, it's been pulled into the Angular CLI repository, and the appropriate schematics have been added to make it easier to use. What that means is that you can now add SSR to your Angular application with a single command: ` and you can generate a new SSR application with: ` And to make SSR a first-class citizen, the Angular team is planning to make it the default in the next version. But surely, it's not only rebranding and some CLI commands, you may say. And you're right. You may remember that hydration in Angular Universal has not been optimal as it was destructive and required essentially a complete re-render of the application, thus pretty much negating the benefits of SSR. With the introduction of non-destructive hydration in Angular 16 and its stability in Angular 17, the Angular team has taken a significant step forward in making SSR a first-class citizen. How to Make an SSR-capable Angular Application Let's look at how to use Angular SSR in practice by creating a simple SSR app. First, we need to create a new Angular application with SSR support: ` That will generate a few server-specific files: - server.ts, which is an Express server configured to handle Angular SSR requests by default but can also be extended to handle API routes, or anything else that Express can handle. - src/app/app.config.server.ts provides configuration for the server application similar to your app.config.ts, which defines the configuration for your client application. Both export an instance of ApplicationConfig - src/main.server.ts bootstraps the server application in a similar way to what main.ts does for your client app by calling the bootstrapApplication function. You should have an SSR application ready just like that. You can verify that by running ng serve. The terminal output should show you information about the server bundles generated, and you should be able to see the default app when going to http://localhost:4200. Open the Developer Tools and check the console to see that hydration is enabled. You'll see a message with hydration-related stats, such as the number of components and nodes hydrated. Speaking of hydration... Client Hydration If you open the src/app/app.config.ts in your new SSR application, you'll notice one interesting provider: provideClientHydration(). This provider is crucial for enabling hydration - a process that restores the server-side rendered application on the client. Hydration improves application performance by avoiding extra work to re-create DOM nodes. Angular tries to match existing DOM elements to the application's structure at runtime and reuses DOM nodes when possible. This results in a performance improvement measured using Core Web Vitals statistics, such as reducing the Interaction To Next Paint (INP), Largest Contentful Paint (LCP), and Cumulative Layout Shift (CLS). Improving these metrics also positively impacts SEO performance. Without hydration enabled, server-side rendered Angular applications will destroy and re-render the application's DOM, which may result in visible UI flicker, negatively impact Core Web Vitals like LCP, and cause layout shifts. Enabling hydration allows the existing DOM to be reused and prevents flickering. Event Replay and Improved Debugging With Angular 18, event replay, a feature that significantly enhances the SSR experience, came into developer preview. Event replay captures user interactions during the SSR phase and replays them once the application is hydrated. This ensures that user actions, such as adding items to a cart, are not lost during the hydration process, providing a seamless user experience even on slow networks. To enable event replay, you can add withEventReplay to your provideClientHydration call: ` Furthermore, Angular 18 takes another step towards making SSR a first-class citizen by enhancing the debugging experience with Angular DevTools. Now, DevTools include overlays and detailed error breakdowns, empowering developers to visualize and troubleshoot hydration issues directly in the browser. Constraints However, hydration imposes a few constraints on your application that are not present without hydration enabled. Your application must have the same generated DOM structure on both the server and the client. The hydration process expects the DOM tree to have the same structure in both environments, including whitespaces and comment nodes produced during server-side rendering. Direct DOM Manipulation The fact that server and client DOM structures must match means that components that manipulate the DOM using native DOM APIs or use innerHTML or outerHTML will encounter hydration errors. The errors are thrown because Angular is unaware of these changes and cannot resolve them during hydration. Examples include accessing the document, querying for specific elements, and injecting additional nodes using appendChild. Detaching DOM nodes and moving them to other locations will also cause errors. Direct DOM manipulation is generally considered bad practice in Angular, so I recommend refactoring your components to avoid it regardless of hydration. However, in warranted cases where refactoring is not feasible, you can use the ngSkipHydration attribute until a hydration-friendly solution can be implemented. You can use it directly in a template: ` or as a host binding: ` > Note: The ngSkipHydration attribute forces Angular to skip hydrating the entire component and its children. So use this attribute carefully, as, for example, applying it to your root application component would effectively disable hydration for the entire application. Valid HTML Structure Another constraint is that invalid HTML structures in component templates can result in DOM mismatch errors during hydration. Common issues include: - without a - inside a - inside an - inside another Again, if you have an invalid HTML, you should fix this regardless of hydration. If you are unsure about your HTML's validity, you can use the W3 validator to check it. Zoneless apps or custom Zone.js Hydration relies on a signal from Zone.js when it becomes stable. Custom or "noop" Zone.js implementations may thus disrupt the timing of this event, triggering the serialization or cleanup process too early or too late. This configuration is not yet fully supported. While Zoneless is already available in developer preview and we could assume this issue will be resolved when it lands as stable, there is no explicit ETA mentioned in the official roadmap. I18n One of the biggest blockers for adoption of client hydration was the lack of support for internationalization with hydration. By default, Angular 17 skipped hydration for components that used i18n blocks, re-rendering them from scratch. In Angular 18, however, hydration for i18n blocks got into developer preview. To enable hydration for i18n blocks, you can add withI18nSupport to your provideClientHydration call. ` Third-Party Libraries with DOM Manipulation The last thing to consider is third-party libraries that manipulate DOM, such as D3 charts. They could cause DOM mismatch errors when hydration is enabled. You can use the ngSkipHydration attribute to skip hydration for components rendered using these libraries. Conclusion The Angular team has been doing great work to make the framework more modern and competitive, hearing the community feedback and adjusting the roadmap accordingly. And SSR is an excellent example of that. With the introduction of non-destructive hydration in Angular 16 and making it stable in Angular 17, the Angular team has taken a significant step forward in making SSR a first-class citizen in Angular. The @angular/ssr package is now part of the Angular CLI, making adding SSR to your Angular application easier. The Angular team plans to make SSR the default in the next version, further solidifying its position in the Angular ecosystem. However, as with any new feature, there are some constraints and best practices to follow when using SSR with Angular. You should be aware of hydration's limitations and ensure your application is compatible with it. However, if you follow the guidelines provided by the Angular team, you should be able to take advantage of the benefits of SSR without any significant issues....

Utilizing Cypress Testing in a Multi-App Monorepo cover image

Utilizing Cypress Testing in a Multi-App Monorepo

For web developers, Cypress is a pretty well-understood testing library that everyone has at least come across or heard of. Getting it set up for an app is pretty straightforward, and you can be off and writing tests in a matter of minutes. But what if you have a monorepo with multiple apps? Do you set up a per-app test suite and manage multiple sets of code in multiple places? Or have you already set that up and noticed that there's a lot of redundancy with potentially shared code that you'd like to refactor into one place? I was recently tasked with setting up such a Cypress testing structure in our Showcase section for our starter.dev project. The idea of the Showcases is that we utilized each of our framework packages to create a GitHub clone as an advanced example of an implementation of each. So they all have the same exact UI to the user, but underneath the hood, they all utilize different sets of technologies for the JavaScript framework, GraphQL/Rest, or CSS libraries. I instantly figured that there had to be a way to write one set of tests that could be utilized against each app, and all I had to do was unify the data-testid attributes across all of the apps. But how do you set it up to automate the process, and against so many different apps? Would it even be possible to start, test, and stop each app through a script? Thankfully, the answer is yes -- and this blog will document and explain a structure that I used when solving that problem. Prerequisites If you've already been developing in a monorepo and have everything set up for that, you likely already have all requirements necessary to install Cypress. However, if you're not and you're setting everything up from scratch, the Cypress docs list a few system requirements: * macOS 10.9 and above (64-bit only) * Linux Ubuntu 12.04 and above, Fedora 21 and Debian 8 (64-bit only) * Windows 7 and above (64-bit only) If you're on a Linux distrobution, pay special attention to the dependencies you'll be needing as well. If you're using npm, you'll need: * Node.js 12 or 14 and above It's possible to download Cypress directly, but I don't recommend that approach for the purposes of this guide. Project Structure For this example, I will be showing the structure I used in the starter.dev GitHub Showcases repository. But hopefully it demonstrates that it's flexible enough to be used on any monorepo structure with any number of apps. The folder structure will look something like this when we're done (showing just two apps and the relevant folders/files for succinctness): ` Installation In the root of your project, you'll want to make your directory where your Cypress tests will exist (replace tests-e2e with whatever you'd like your folder to be called): ` Then, once inside this folder, install Cypress via npm: ` Or via yarn: ` Next, we'll install start-server-and-test, which will be needed later on to automate starting our apps and running our test suite against them. Let's also install TypeScript: ` Or via yarn: ` In the newly created package.json in this folder, let's add a basic script to open Cypress: ` Configuration Now that we have everything installed, we can start configuring Cypress. Let's open Cypress via the newly created script in our last step, npm run cypress:open or yarn run cypress:open. Once Cypress opens, select the E2E Testing configuration and click continue at the bottom of the list to create all the default files (make sure to read what each one does if this is your first time using Cypress!). You should see in your folder structure that Cypress created a number of files and folders automatically, but let's create a few additional folders: ` Inside cypress/configs let's create a file called app1.config.js, or app1.json if you're using a Cypress version older than 10 (replace app1 with the name of one of the apps you want to test against): ` Just regular JSON format for versions prior to Cypress 10: ` A couple notes, baseUrl is the URL your app will deploy on when started up, startCommand is the commmand your app uses to start (for me, it was different for a few different apps, but if yours all use the same command you may not need this), and integrationFolder is where all the test .spec files will be. This can be customized if you've already decided that you'd like your tests to be written separately and/or only one of them will need unique tests, etc. But we can leave it alone for now. Additionally, Cypress has quite a few configuration options. But, the one I'd like to point out specifically is the env option. Just like a .env file, you can utilize this to pass in specific parameters or options into your Cypress tests. Specifically, the different apps I was getting this test suite working against handled auth differently in a few cases, so I needed to visit a specific URL to fire off a redirect/auth chain to mock its state. It looks like this (will be the same in any version of Cypress): ` This may not be something you specifically need yourself, but you can pass in whatever you need for your specific apps, and it will only inject into the Cypress state for that app's Cypress config file. So, maybe you could use this to get the name of what app you're testing, etc. Next, let's add a basic test file inside cypress/e2e or cypress/integration if under Cypress 10. Call it whatever you'd like, but I'll name it first-test.cy.ts (or first-test.spec.ts if you're using a Cypress version older than 10): ` Lastly, let's set up a few run scripts to automate running against all our apps. Update the scripts section of your package.json as such: ` For Cypress versions prior to 10: ` Tying it all together If your entire project structure is set up properly and the file names and configurations all match names properly as described at the start of this blog, the scripts should work as is. The usage would looks like this: ` ` The first command, test, runs through all of the test specs that your config file you set up points to in the integrationFolder option in the command line. This option is good to quickly verify passing tests in the background and/or on your CI/CD pipeline. It first fetches the config file from cypress/config/, grabs the baseUrl from that config file, utilizes start-server-and-test to start your target app, and once it's running, it will run your test suite and tear everything down. This is a powerful and flexible option to then chain together running all your Cypress test suites for all your apps back to back from the same place. The next option, test:watch is the option you'll want to use when developing tests. All it does is open Cypress against the target app's config file you've set up. Then in another process, you will still manually need to start your app locally. The benefit of this is once you change code in either the app or your Cypress test spec files, both will update automatically while everything is still open. Conclusion The solution laid out here isn't one for every single monorepo. However, I believe it can eliminate redundancy for certain types of monorepo structures where each app inside is similar enough, or even more rarely, each app _is_ the same but the target deployments or underlying technology is different. Instead of a per-app Cypress installation and test suite in each app package, this may be exactly the solution to abstract or even refactor them to one single place. There's even flexibility built into this structure to allow a unique set of tests against only one or some of the apps you will need to have integration tests for. Of course, this is only the first step. But hopefully it eliminates potentially the most problematic one. If you'd like to read further on writing Cypress tests themselves, we also have a great guide on writing tests themselves with Cypress that you can check out....

Vercel BotID: The Invisible Bot Protection You Needed cover image

Vercel BotID: The Invisible Bot Protection You Needed

Nowadays, bots do not act like “bots”. They can execute JavaScript, solve CAPTCHAs, and navigate as real users. Traditional defenses often fail to meet expectations or frustrate genuine users. That’s why Vercel created BotID, an invisible CAPTCHA that has real-time protections against sophisticated bots that help you protect your critical endpoints. In this blog post, we will explore why you should care about this new tool, how to set it up, its use cases, and some key considerations to take into account. We will be using Next.js for our examples, but please note that this tool is not tied to this framework alone; the only requirement is that your app is deployed and running on Vercel. Why Should You Care? Think about these scenarios: - Checkout flows are overwhelmed by scalpers - Signup forms inundated with fake registrations - API endpoints draining resources with malicious requests They all impact you and your users in a negative way. For example, when bots flood your checkout page, real customers are unable to complete their purchases, resulting in your business losing money and damaging customer trust. Fake signups clutter the app, slowing things down and making user data unreliable. When someone deliberately overloads your app’s API, it can crash or become unusable, making users angry and creating a significant issue for you, the owner. BotID automatically detects and filters bots attempting to perform any of the above actions without interfering with real users. How does it work? A lightweight first-party script quickly gathers a high set of browser & environment signals (this takes ~30ms, really fast so no worry about performance issues), packages them into an opaque token, and sends that token with protected requests via the rewritten challenge/proxy path + header; Vercel’s edge scores it, attaches a verdict, and checkBotId() function simply reads that verdict so your code can allow or block. We will see how this is implemented in a second! But first, let’s get started. Getting Started in Minutes 1. Install the SDK: ` 1. Configure redirects Wrap your next.config.ts with BotID’s helper. This sets up the right rewrites so BotID can do its job (and not get blocked by ad blockers, extensions, etc.): ` 2. Integrate the client on public-facing pages (where BotID runs checks): Declare which routes are protected so BotID can attach special headers when a real user triggers those routes. We need to create instrumentation-client.ts (place it in the root of your application or inside a src folder) and initialize BotID once: ` instrumentation-client.ts runs before the app hydrates, so it’s a perfect place for a global setup! If we have an inferior Next.js version than 15.3, then we would need to use a different approach. We need to render the React component inside the pages or layouts you want to protect, specifying the protected routes: ` 3. Verify requests on your server or API: ` - NOTE: checkBotId() will fail if the route wasn’t listed on the client, because the client is what attaches the special headers that let the edge classify the request! You’re all set - your routes are now protected! In development, checkBotId() function will always return isBot = false so you can build without friction. To disable this, you can override the options for development: ` What happens on a failed check? In our example above, if the check failed, we return a 403, but it is mostly up to you what to do in this case; the most common approaches for this scenario are: - Hard block with a 403 for obviously automated traffic (just what we did in the example above) - Soft fail (generic error/“try again”) when you want to be cautious. - Step-up (require login, email verification, or other business logic). Remember, although rare, false positives can occur, so it’s up to you to determine how you want to balance your fail strategy between security, UX, telemetry, and attacker behavior. checkBotId() So far, we have seen how to use the property isBot from checkBotId(), but there are a few more properties that you can leverage from it. There are: isHuman (boolean): true when BotID classifies the request as a real human session (i.e., a clear “pass”). BotID is designed to return an unambiguous yes/no, so you can gate actions easily. isBot (boolean): We already saw this one. It will be true when the request is classified as automated traffic. isVerifiedBot (boolean): Here comes a less obvious property. Vercel maintains and continuously updates a comprehensive directory of known legitimate bots from across the internet. This directory is regularly updated to include new legitimate services as they emerge. This could be helpful for allowlists or custom logic per bot. We will see an example in a sec. verifiedBotName? (string): The name for the specific verified bot (e.g., “claude-user”). verifiedBotCategory? (string): The type of the verified bot (e.g., “webhook”, “advertising”, “ai_assistant”). bypassed (boolean): it is true if the request skipped BotID check due to a configured Firewall bypass (custom or system). You could use this flag to avoid taking bot-based actions when you’ve explicitly bypassed protection. Handling Verified Bots - NOTE: Handling verified bots is available in botid@1.5.0 and above. It might be the case that you don’t want to block some verified bots because they are not causing damage to you or your users, as it can sometimes be the case for AI-related bots that fetch your site to give information to a user. We can use the properties related to verified bots from checkBotId() to handle these scenarios: ` Choosing your BotID mode When leveraging BotID, you can choose between 2 modes: - Basic Mode: Instant session-based protection, available for all Vercel plans. - Deep Analysis Mode: Enhanced Kasada-powered detection, only available for Pro and Enterprise plan users. Using this mode, you will leverage a more advanced detection and will block the hardest to catch bots To specify the mode you want, you must do so in both the client and the server. This is important because if either of the two does not match, the verification will fail! ` Conclusion Stop chasing bots - let BotID handle them for you! Bots are and will get smarter and more sophisticated. BotID gives you a simple way to push back without slowing your customers down. It is simple to install, customize, and use. Stronger protection equals fewer headaches. Add BotID, ship with confidence, and let the bots trample into a wall without knowing what’s going on....

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