As part of the todo-tracker project, I transitioned the project from JavaScript to TypeScript. This transition wasn't just about changing file extensions; it represented a shift in how I approached type safety, error handling, and data validation. Through this blog, I'll share the key differences and improvements I encountered, especially focusing on error checking, null safety, and data validation using Zod.
Data Validation & Errors
Parsing JSON Files
In the original JavaScript version, the diff.js
file functioned but lacked in terms of data validation and error handling:
// JavaScript - diff.js
function read(filePath) {
const data = fs.readFileSync(filePath, 'utf8');
return JSON.parse(data);
}
const base_tasks = read('./output-base.json');
const new_tasks = read('./output-new.json');
Here, the read
function straightforwardly reads and parses JSON files without any form of structure validation. This approach works under ideal circumstances but falls short when unexpected data structures arise, potentially leading to runtime errors.
Transitioning to TypeScript, I integrated Zod for data validation, significantly improving the reliability and maintainability of the code:
// TypeScript - diff.ts
import { TASK_FILE } from './schema';
function read(path: string) {
const data = fs.readFileSync(path, 'utf8');
const result = TASK_FILE.safeParse(JSON.parse(data));
if (!result.success) throw new Error('Invalid JSON structure');
return result.data;
}
const base_tasks = read('./output-base.json');
const new_tasks = read('./output-new.json');
In this TypeScript version, TASK_FILE.safeParse()
ensures that the JSON data adheres to a predefined schema before the application proceeds. This not only catches errors early but also provides clear diagnostics, thereby reducing runtime errors and debugging time.
TypeScript null and Error Checking
TypeScript's strict type system has forced me to handle potential errors and null values explicitly, which was a marked improvement over the JavaScript implementation. One notable example is the URL parsing in the parser module:
In JavaScript, the URL parsing and worker instantiation were implicitly assumed to be successful:
// JavaScript - parser.js
for (const chunk of chunks) {
const worker = new Worker(new URL('./file_worker.js', import.meta.url));
workers.push(worker);
...
However, in TypeScript, I had to handle potential null values explicitly, enhancing the reliability of the code:
// TypeScript - parser.ts
const url = new URL("./file_worker.js", import.meta.url);
if (!url) throw new Error("URL of file_worker.js not found");
for (const chunk of chunks) {
const worker = new Worker(url.toString());
workers.push(worker);
...
This explicit null checking ensures that the code does not proceed with undefined or null values, thus avoiding possible runtime errors.
Schema Validation with Zod
One of the transformative aspects of this transition was the use of Zod for schema validation. Zod turned ambiguous JSON parsing into a structured and validated process, particularly evident in the configuration and task parsing. The previous javascript simply checked for 'tags' property in the config:
// read using bun file
const file = Bun.file(`${args.config}`);
const config = await file.json();
if (!config.tags) throw new Error("No tags found in config file");
However typescript using the schema defined in schema.ts
would check for correct structure of the entire config, that meant tags
and the properties for each tag, and the ignore
regex array.
// read using bun file
const file = Bun.file(`${args.config}`);
const config = await file.json();
// validate with zod CONFIG_SCHEMA
const result = CONFIG_SCHEMA.safeParse(config);
if (!result.success) throw new Error(result.error.message);
By defining clear schemas for expected data structures, I could catch and handle errors systematically, enhancing the robustness of the application and ensuring data integrity.
Conclusion
Transitioning from JavaScript to TypeScript in my to-do tracker project has been a journey of learning and improvement. The introduction of strict types, coupled with the powerful schema validation provided by Zod, has fundamentally changed how I write and think about code. These changes have resulted in a more reliable, maintainable, and error-resistant application, demonstrating the undeniable benefits of TypeScript in enhancing code quality and developer experience. This transition has not only improved the current project but has also declared the structure of the types and functions which will be useful when moving to more 'defined' languages in the future.