Quick Summary
This insight explains how Bacancy handled a JavaScript to TypeScript migration for a US healthcare client operating a patient scheduling and electronic health records platform. It outlines what led the client to migrate, the factors evaluated before the migration began, the step-by-step process followed during execution, and the outcomes recorded 60 days after deployment.
Introduction
In early 2026, a healthcare technology company based in the US approached Bacancy for a JavaScript to TypeScript migration on their patient scheduling and electronic health records platform. Their platform was used by mid-sized clinics and specialty healthcare providers across seven states for patient scheduling and electronic health record management. The frontend was developed in React using plain JavaScript, while the backend operated on Node.js, also written in JavaScript. The application had been actively developed for four years, during which the engineering team expanded from three developers to eleven.
The platform itself was functioning. Appointments were being scheduled successfully, patient records were managed daily, and the system was handling nearly 40,000 patient interactions every month. However, three major issues had started surfacing repeatedly, and the team could no longer continue working around them with temporary fixes.
The first issue was the growing number of runtime errors as the development team scaled. Developers were often calling functions expecting one structure of data but receiving something slightly different, a missing field, a date stored as a string instead of a Date object, or an unexpected null value. These issues typically surfaced only after clinicians encountered them in production. In a healthcare system where incorrect patient data is not merely a software defect but potentially a compliance and safety issue, this risk had become unacceptable.
The second challenge involved developer onboarding. The codebase had expanded to nearly 87,000 lines of JavaScript without consistent type documentation or interface definitions. New developers had no straightforward way to understand what a function accepted or returned without tracing through the implementation manually. Senior developers were spending close to 30–40% of their review time identifying type-related mistakes that ideally should have been caught automatically during development.
The third issue was the client’s upcoming product roadmap. The company planned to introduce a pharmacy integration module along with a telehealth layer over the following twelve months. The VP of Engineering was reluctant to build two major product extensions on top of a JavaScript codebase already struggling under its current scale.
Before contacting us, the client considered two possible approaches:
- Rebuild the entire platform from scratch using TypeScript with updated tooling and architecture.
- Incrementally migrate the existing JavaScript codebase to TypeScript while preserving four years of proven business logic.
For a live healthcare platform, a full rewrite carried significant operational risk. Business logic accumulated over the years often contains hidden edge cases that only become visible under real production conditions. Because of this, the client decided on an incremental migration strategy, and we executed the engagement over a period of 14 weeks.
Running a JavaScript application that's becoming harder to maintain as your team scales?
Hire JavaScript developer from Bacancy to stabilize your existing codebase while you plan your migration path.
Was It the Right Time to Move From JavaScript to TypeScript?
This is the first question we discuss with every client before beginning any JavaScript to TypeScript migration. The two languages serve overlapping purposes, and the differences only become important at a certain scale, our breakdown of TypeScript vs JavaScript covers where each one fits. Migrating from JavaScript to TypeScript is not automatically the right decision in every scenario. In some cases, it creates substantial long-term value. In others, the migration effort may outweigh the benefits.
Industry adoption patterns provide useful context here. The State of JavaScript 2025 survey found that 40% of developers now write exclusively in TypeScript, up from 28% in 2022, while only 6% use plain JavaScript exclusively. The industry has clearly moved, but the question for any individual codebase is whether the move is worth the cost right now.
For this client, the timing made sense for four specific reasons.
The codebase had grown large enough that JavaScript’s flexibility was becoming a problem rather than an advantage.
With 87,000 lines of code spread across frontend and backend systems, no single developer fully understood every function contract throughout the application. TypeScript would make those contracts explicit, searchable, and enforceable rather than dependent on tribal knowledge.
The engineering team was scaling faster than shared knowledge could keep pace with.
Eleven developers were now contributing to a platform initially built by just three engineers. Most team members were regularly modifying code they had not originally written. TypeScript ensured every developer could work against clearly defined interfaces regardless of when they joined the project.
The planned expansion increased the cost of delaying migration.
Building pharmacy integrations and telehealth capabilities on top of a codebase already experiencing runtime production issues would only compound complexity over time. Migrating before introducing those modules was significantly more manageable than attempting it afterward.
A missing field causing a visual issue in a consumer app is inconvenient. The same issue affecting medication records or appointment workflows in a healthcare environment can become a patient safety and compliance concern. The client’s compliance team had already identified two PHI-adjacent incidents in the previous six months caused by type-related issues reaching production.
In this situation, the answer was clear. The migration timing was appropriate.
Factors We Evaluated Before the JavaScript to TypeScript Migration
Before introducing any TypeScript into the codebase, we spent two full weeks on discovery and technical assessment. The objective was to understand the actual condition of the application so the migration strategy reflected the realities of the system instead of relying on a generic migration template.
Codebase Size and Complexity
The platform consisted of 87,000 lines of JavaScript distributed across a React frontend and a Node.js/Express backend. The frontend contained 340 components, while the backend included 214 API route handlers and 68 shared utility modules. The scale alone indicated the migration needed to happen in controlled phases rather than through a single large conversion effort.
Existing Test Coverage
The backend had 62% test coverage, while the frontend coverage stood at 44%. This was one of the most critical metrics during the assessment. Migrating to TypeScript changes how code is structured and validated, so reliable tests are essential to ensure existing functionality remains stable. The backend coverage was acceptable for migration. The frontend coverage, however, required improvement before touching higher-risk components.
Third-Party Dependencies
The project relied on 94 npm packages. We audited each dependency to verify TypeScript support and type definition availability. Out of the total:
- 71 packages shipped official type definitions or bundled types
- 18 relied on community-maintained typings of mixed quality
- 5 had no available typings and required custom declaration files
PHI Data Flows
Since the application handled patient information, we mapped all PHI-related data flows before defining any interfaces. Patient names, dates of birth, medical record numbers, appointments, and clinical notes all required consistent typing across frontend components, backend APIs, and database layers. Inconsistent type definitions across layers are one of the primary ways runtime issues reappear even after adopting TypeScript.
Team Readiness
Among the client’s eleven developers, seven had previous experience working with TypeScript, while four had worked exclusively with JavaScript. Before the migration began, we conducted a two-day internal TypeScript workshop focused specifically on the patterns and practices the team would use throughout the project.
Step-by-Step JavaScript to TypeScript Migration Process We Followed
The first step involved installing TypeScript and introducing tsconfig.json files into both frontend and backend repositories. We configured allowJs: true and checkJs: false, which allowed TypeScript to coexist with JavaScript without immediately validating existing files. The application continued functioning exactly as before while developers gradually began introducing TypeScript into new code.
At this stage, we also kept strict: false. Enabling strict mode immediately would have generated thousands of compiler errors before any foundational types existed, making meaningful progress difficult to track.
This phase took three days. By the end, the application compiled successfully with TypeScript enabled, and all existing tests continued passing.
Step 2: Create Core Shared Data Types First
Before migrating individual components or route handlers, we defined TypeScript interfaces for the platform’s core business entities, patient records, appointments, clinician profiles, prescription data, and insurance information.
We intentionally started here because these entities were shared throughout the application. Migrating components before defining shared interfaces would have resulted in inconsistent versions of the same data structures across the system, exactly the kind of issue TypeScript is meant to prevent.
The shared interfaces were placed inside a central types/ directory accessible to both frontend and backend applications. In total, we created 34 core interfaces covering PHI-related entities and the most common API request and response contracts.
This phase took five days, including the creation of custom declaration files for packages lacking official typings.
Step 3: Migrate the Backend API Layer
We chose to migrate the backend first because it defined the API contracts consumed by the frontend. Establishing typed backend contracts early allowed frontend developers to reuse those definitions directly instead of redefining them independently.
Each API route handler was migrated module by module:
- Files were renamed from
.js to .ts
- Request and response objects were typed
- Existing tests were executed after every migration
- Compiler errors were resolved incrementally
The backend consisted of 214 route handlers grouped into 22 modules. Migration order was prioritised based on test coverage, with the most thoroughly tested routes migrated first.
Several recurring issues surfaced during backend migration.
Database query result typing: The application used a raw query library returning untyped results. Once return types were defined, we identified six areas where the code assumed values were always present despite nullable database fields. All six cases represented real PHI-adjacent bugs.
Inconsistent error handling: Different route handlers passed differently structured error objects into middleware layers. Typing these flows exposed the inconsistencies and allowed us to standardise API error handling.
Utility functions with unclear contracts: The shared utility layer had evolved organically over several years. Some functions supported multiple undocumented call signatures depending on the usage context. Once typed, twelve functions revealed conflicting usage patterns across different modules that had previously “worked” only by coincidence.
The backend portion of the JavaScript to TypeScript migration required four weeks.
Step 4: Migrate Frontend Components
With backend contracts stabilised, we shifted focus to the frontend. The 340 React components were categorised into four migration tiers:
- Tier 1: Stateless presentational components – 94 components
- Tier 2: Components receiving props from parents – 112 components
- Tier 3: Components directly fetching API data – 87 components
- Tier 4: Complex stateful components managing PHI-related workflows – 47 components
Migration followed this exact order. Presentational components provided a safe starting point because they involved no data fetching or state management. This allowed us to validate shared types within the frontend layer before touching more complex functionality.
Tier 4 components required the most caution. These components handled appointment scheduling, clinical notes, and patient record workflows. Before migrating them, we increased their test coverage from 44% to 71% and ensured every PHI-related type definition underwent review by at least two developers.
One particularly important issue surfaced during frontend migration. In the appointment scheduling flow, patient dates of birth were treated inconsistently, sometimes as strings and sometimes as Date objects. In most situations, JavaScript silently parsed the values successfully. However, for certain edge cases involving patients born before the year 2000, incorrect age calculations could occur without visible errors. TypeScript exposed this inconsistency during migration. The eventual fix required only two lines of code, but the underlying issue could have created inaccurate patient data within clinical workflows.
Frontend migration took five weeks.
Step 5: Gradually Enable Strict Mode
After all files had been converted to TypeScript, we enabled strict mode incrementally rather than activating every rule simultaneously.
We began with strictNullChecks, the setting most closely related to the client’s original runtime issues. Enabling it generated 312 compiler errors across the codebase. Over the next week, we categorised and resolved them:
- 187 represented genuine null-handling issues and real bugs
- 94 involved values developers knew were safe but had not formally proven to TypeScript
- 31 existed within outdated test implementations
- Next, we enabled
noImplicitAny, which revealed 89 additional untyped variables.
Finally, we enabled strictFunctionTypes and strictPropertyInitialization, which produced 44 and 27 additional issues respectively. By the end of this phase, the entire application operated successfully with full strict mode enabled.
This phase took two weeks.
Step 6: Update CI/CD Pipelines and Enforce Standards
The final step focused on preventing regression. Without CI-level enforcement, TypeScript migrations often deteriorate over time as developers introduce shortcuts under delivery pressure. The pipeline rules below align with the broader TypeScript best practices we apply across all post-migration codebases.
We introduced three key safeguards into the CI pipeline:
tsc --noEmit runs against every pull request and blocks merges if type errors exist
- ESLint rules prohibit
@ts-ignore without documented approval and justification
- Explicit
any usage is blocked entirely in PHI-related modules and flagged elsewhere with warning requirements
We also updated onboarding documentation and code review processes to include TypeScript-specific guidelines, covering API typing practices, nullable PHI handling, and appropriate use of assertions versus runtime validation.
This final step took three days.
Planning to build a greenfield application instead of migrating an existing one?
Get TypeScript development services from Bacancy to deliver production-ready, strict-mode codebases from the first commit.
Conclusion
The complete JavaScript to TypeScript migration was delivered in 14 weeks. Below are the measurable outcomes observed during the first 60 days after deployment.
| Metric
| Before Migration
| 60 Days After
|
|---|
| Runtime type-related production incidents
| 23 incidents in prior 60 days
| 2 incidents
|
| Average code review time per PR
| 47 minutes
| 31 minutes
|
| Developer onboarding time to productive PRs
| 3–4 weeks
| 10–12 days
|
| Backend test coverage
| 62%
| 79%
|
| Frontend test coverage for PHI components
| 44%
| 71%
|
| PHI-related bugs caught before production
| Not measurable
| 8 compile-time detections
|
| Pharmacy integration module status
| Not started
| Development underway with typed foundations
|
The two runtime incidents recorded after migration both originated from third-party integrations, specifically pharmacy APIs and insurance verification services returning unexpected payloads. In both cases, TypeScript exposed the mismatch immediately, allowing the team to resolve the issue within hours instead of allowing incorrect patient data to propagate silently.
The null-handling issues uncovered during backend migration and the date parsing inconsistency found during frontend migration were not caused by careless engineering. They were symptoms of a codebase that had grown beyond the limits of what JavaScript’s runtime-only validation could reliably manage. TypeScript did not replace developer skill; it amplified it by exposing issues that would otherwise remain hidden across tens of thousands of lines of code.
The pharmacy integration module, the first major feature developed entirely in TypeScript after migration, shipped its initial version six weeks later, on schedule, with zero type-related production issues during the first two weeks after launch.
Need Help Migrating Your JavaScript Codebase to TypeScript?
Every JavaScript application evolves differently, and the right migration strategy depends on factors such as codebase scale, testing maturity, framework choices, and the sensitivity of the data your application manages.
If your team is considering a JavaScript to TypeScript migration and wants to execute it without disrupting ongoing product delivery, our engineers have handled migrations on production systems, including regulated healthcare environments where reliability and correctness are essential.
Hire TypeScript developers from Bacancy who can help you migrate confidently with a structured, production-safe approach.
Frequently Asked Questions (FAQs)
The time required depends on the size of the application and how complex the codebase is. Smaller projects may take a few weeks, while large production applications usually take a few months when migrated step by step. In this case study, the migration of an 87,000-line healthcare platform was completed in 14 weeks.
In most cases, migrating the existing application is the better option. Rebuilding from scratch can create unnecessary risks, especially for platforms that have been running in production for years. A phased migration allows teams to keep the existing business logic while gradually improving code quality and type safety.
For large applications, yes. TypeScript helps catch errors earlier, improves code readability, and makes it easier for developers to work on the same codebase. It becomes even more useful when the application is growing quickly or handling sensitive data like healthcare or financial records.
Some common issues include missing test coverage, inconsistent data structures, unclear function usage, and third-party packages without proper TypeScript support. Many hidden bugs also become visible during migration because TypeScript starts checking areas that JavaScript normally ignores.
Yes. Most teams migrate gradually instead of rewriting everything at once. JavaScript and TypeScript can run together in the same project, which allows developers to continue building features while the migration happens in phases.