Build a Video Chat App with ASP.NET Core and Angular using Twilio Video
Time to read:
Video is everywhere now, from telehealth visits to virtual banking to online exams. With Twilio Programmable Video, you can add multi-party video to your own app without running any media servers yourself.
In this tutorial, you will build a small but complete group video chat app: an ASP.NET Core Web API that mints Twilio access tokens and lists active rooms, and an Angular single-page app that joins a room, shows every participant, and lets you pick your camera and microphone. To keep the room list current for everyone, the server pushes updates over SignalR instead of making the browser poll.
By the end you will have a working two-participant call running locally, with device selection and a live room list that updates the moment someone joins or leaves.
What you will build
The app has two parts that run side by side:
- An ASP.NET Core Web API which will hand the browser a Twilio access token, report which rooms currently have people in them, and relay a "rooms changed" signal to every connected client over SignalR.
- An Angular SPA that uses the twilio-video library to capture the camera and microphone, connect to a room, and render each participant, plus the @microsoft/signalr client to keep its room list live.
Twilio mints the access token's signature from your API Key Secret, so the token endpoint never has to call Twilio at all. The room and participant lookups call the Twilio REST API.
Prerequisites
To follow along you will need:
- A Twilio account.
- The .NET SDK 10 or later.
- Node.js 22 LTS or later. The Angular CLI requires a current Node version.
- The Angular CLI, installed globally with
npm install -g @angular/cli. (If you would rather not install it globally, replace every ng command below withnpx @angular/cli.) - A code editor such as Visual Studio Code with the C# Dev Kit.
Everything here runs on macOS, Linux, and Windows alike.
Create a Twilio Video API Key
The server signs access tokens with a Twilio API Key SID and API Key Secret. To create one, in the Twilio Console, go to Account > API keys & tokens and create a new Standard API key. Copy the SID (it starts with SK) and the Secret shown once at creation time.
You will also need your Account SID (it starts with AC), shown on the Console dashboard.
Keep these three values handy: AccountSid, ApiKey, and ApiSecret. Treat the secret like a password and never commit it to source control.
Build the ASP.NET Core Web API
Start by scaffolding a minimal Web API. From the folder where you want the project to live, run:
This creates a Server project using .NET's minimal hosting model, where everything is configured in Program.cs with no Startup.cs. You will replace Program.cs entirely below, which removes the sample WeatherForecast endpoint the template generates.
Next, add the Twilio .NET helper library. Pin the version so the tutorial stays reproducible as new majors ship:
Pin the development port
dotnet new webapi writes a random local port into Properties/launchSettings.json – so note the port on your machine will differ from mine. The Angular dev proxy you set up later points at a fixed port, so pin the API to http://localhost:5216 now. Open Server/Properties/launchSettings.json and set the http profile's applicationUrl:
dotnet run uses this http profile by default, so the API will listen on port 5216.
Again, the Angular dev proxy will point at a fixed port, so if you prefer a different port, use it consistently here, in the test URL below, and in proxy.conf.json later.
Bind the Twilio credentials to a typed options class
Rather than reading configuration strings by key throughout the app, define a small options class and bind it once. Create TwilioOptions.cs:
Add a matching (empty) section to appsettings.json so the shape is documented in the repo, but leave the real values out of the file. Merge this Twilio key into the existing JSON object alongside Logging and AllowedHosts; do not replace the file:
Store the credentials with the Secret Manager so they don’t touch your source tree:
Define a model for the room list
The client needs a simple shape describing each active room. Create Models/ RoomDetails.cs:
Wrap Twilio in a service
Put the two pieces of Twilio logic, minting tokens and reading rooms, into one service. Create Services/TwilioVideoService.cs:
A VideoGrant with no room name lets the token holder join any room, which is what we want for this demo. Token.ToJwt() signs the token with your API Key Secret and returns the JWT string. TwilioClient.Init sets up the credentials the REST calls use; RoomResource.ReadAsync and the nested ParticipantResource.ReadAsync then fetch the in-progress rooms and who is connected to each.
Add the API controllers
The token controller returns a token for a supplied identity, or generates a guest identity if none is given. Create Controllers/ TokenController.cs:
The rooms controller exposes the active-room list. Create Controllers/ RoomsController.cs:
Add the SignalR hub
Polling /api/rooms on a timer would work, but it is wasteful and slow to react. Instead, use a SignalR hub so any client can announce that the rooms have changed, and the server would relay that message to the others. Create Hubs/ NotificationHub.cs:
The notifySelf flag lets the caller decide whether it also wants the echo. The Angular client passes true so the person who just joined refreshes their own list too.
Wire everything up in Program.cs
Replace the contents of Program.cs with the following:
This registers the services, maps the two controllers and the SignalR hub, and sets up static-file serving with a fallback to index.html.
In development, you will run the Angular app on its own dev server, so the CORS policy allows requests from http://localhost:4200. In production the API serves the built Angular files directly, so no CORS is needed.
Run dotnet run and browse to http://localhost:5216/api/token?identity=alice. You should get back a JSON object with a token and an identity. If you paste the token into jwt.io, you will see a grant containing video.
Build the Angular app
With the API in place, scaffold the Angular front end. From the repository root:
This creates a current Angular workspace in a ClientApp folder, using standalone components and signals with no NgModules. Then install the Twilio Video and SignalR client libraries:
Proxy API calls to the backend in development
During development the Angular dev server (port 4200) and the API (port 5216) run separately. Tell the dev server to forward API and SignalR requests to the backend by creating ClientApp/proxy.conf.json:
The "ws": true on /hub is important: it lets the SignalR websocket pass through the proxy. Then point the serve target at this file, send the production build into the API's wwwroot, and allow the CommonJS twilio-video package, by editing ClientApp/angular.json.
Next, add these to the build options:
The twilio-video library is sizable, so the default production bundle budget would emit a warning. Raise the initial budget under configurations > production > budgets so the production build stays clean:
And add the proxy to the serve options:
Describe the API responses
Create ClientApp/src/app/models.ts with interfaces matching what the API returns:
Provide the HTTP client
The services use Angular's HttpClient, so register it in ClientApp/src/app/ app.config.ts:
The video chat service
This service owns the Twilio Video session. It fetches a token, lists cameras and microphones, connects to a room, and exposes the local tracks and remote participants as signals so the UI reacts as people come and go. Create ClientApp/src/app/ videochat.service.ts:
createLocalTracks opens the camera and microphone (optionally the exact devices the user picked), and connect joins the room with those tracks. The participantConnected and participantDisconnected events keep the participants signal in sync. Because Angular's zoneless change detection reacts to signal writes, the UI updates on its own.
The rooms service
This service keeps the active-room list current. It loads the list from the API and opens the SignalR connection; whenever the hub sends RoomsUpdated, it re-fetches. Create ClientApp/src/app/ rooms.service.ts:
The participant component
Each participant, local or remote, gets a tile. This component attaches the participant's Twilio video and audio tracks into a container element and listens for tracks being added or removed. Create ClientApp/src/app/ participant.ts:
Twilio's track.attach() creates a ready-to-play <video> or <audio> element, and track.detach() returns the elements it created so you can remove them. We skip our own audio track to avoid hearing an echo, and mirror the local video preview so it feels like a mirror.
The in-call room view
This component shows the local tile plus a tile for every remote participant, and a button to leave. Create ClientApp/src/app/ video-room.ts:
Notice the built-in control flow: @let names the current room, @if shows the call only while connected, and @for renders one app-participant per remote participant, tracked by participant.sid. When you leave, the component tells the rooms service to notify everyone so their lists update.
The device picker
Before joining, the user should be able to choose a camera and microphone. This component enumerates the devices and binds the chosen IDs back to the parent through two-way model signals. Create ClientApp/src/app/ device-select.ts:
Browsers only reveal device labels after the user grants media permission, so the component asks for the camera and microphone once, stops those tracks immediately, and then enumerates the now-labeled devices.
The active-rooms list
This component renders the live room list and emits the chosen room name when one is clicked. Create ClientApp/src/app/ rooms-list.ts:
The root component
Finally, tie it together. The root component shows the lobby (name, device picker, room name, join button, and room list) until you are connected, then swaps to the call view. Replace ClientApp/src/app/app.ts with:
Replace ClientApp/src/app/app.html with the template:
The root component starts the SignalR connection in ngOnInit, so the room list is live as soon as the page loads. When video.room() is set, the template swaps the lobby for the call view. You can add the styles from the companion repository's app.css to taste.
Run and test the app
Run the two pieces in separate terminals. First the API:
Then the Angular dev server:
Open http://localhost:4200. To test a real call, open the app in two browser tabs:
The video chat lobby: the camera and microphone selectors, a room name field, and the live Active rooms list.
- In each tab, allow camera and microphone access and confirm the Camera and Microphone dropdowns list your devices.
- Enter the same room name in both tabs and click Join room in each.
- Each tab shows your own mirrored preview plus the other participant's live video and audio. That is your two-participant call.
- Watch the Active rooms list. It updates the instant someone joins or leaves, pushed over SignalR, with no page refresh. Click Leave in one tab and the count drops in the other.
Build for production
For production, you want a single deployable that serves both the API and the SPA. You already pointed angular.json at ../Server/wwwroot, so an Angular production build lands in the API's wwwroot. The last piece is to run that Angular build automatically as part of a Release build, so dotnet publish produces everything in one step.
Open Server/VideoChat.Server.csproj and add a SpaRoot property and an MSBuild target that compiles the Angular app on a Release build. Add the SpaRoot line to the existing <PropertyGroup>:
Then add this target inside the <Project> element:
Now a Release publish builds the SPA and bundles it with the API:
The published app serves the compiled Angular files from wwwroot, with MapFallbackToFile handling client-side routes, and exposes the same /api and /hub endpoints, all from one origin. Because the target only runs for Release builds, your day-to-day dotnet run in development stays fast and leaves the SPA to ng serve.
Conclusion
You now have a working group video chat app on current .NET and Angular: an ASP.NET Core Web API that mints Twilio access tokens and lists rooms, an Angular SPA that captures media and renders participants, and SignalR keeping every client's room list live. From here you could add screen sharing, a text chat data track, recording, or authentication so tokens are tied to real users.
The complete source code is available on GitHub at github.com/donaltoomey/build-video-chat-app-dotnet-angular.
Additional resources
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.