Skip to main content

A Beginner’s Guide to TypeScript for Backend Development

TypeScript enhances JavaScript by adding static type checking and type safety, making your code more dependable and easier to understand. As a developer familiar with programming basics, let's jump into TypeScript, exploring its features, best practices, and practical uses. TypeScript helps detect errors early and improves maintainability, making it vital for modern web development. In this guide, we will cover everything you need to know to start using TypeScript and improve your skills.

1. Starting with TypeScript

When setting up a TypeScript project with Convex, you start with a tsconfig.json file. This file lets you configure the TypeScript compiler options, such as the target JavaScript version and module system. For example:

// tsconfig.json
{
"compilerOptions": {
"target": "es6", // Specify ECMAScript target version
"module": "commonjs", // Specify module code generation
"strict": true, // Enable all strict type-checking options
"outDir": "./dist", // Redirect output to the dist folder
"rootDir": "./src" // Specify the root directory of input files
}
}

You can then initialize a TypeScript project using the command line and start writing your first TypeScript code.

2. Setting Up a TypeScript Development Environment

To make the most of TypeScript, set up a development environment with a code editor like Visual Studio Code and install necessary tools such as the TypeScript compiler and a linter. Use npm to install the TypeScript compiler and dependencies:

# Install TypeScript compiler and dependencies
npm install --save-dev typescript @types/node
npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

Create an .eslintrc.js file for linting:

module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
extends: ['plugin:@typescript-eslint/recommended']
}

Add these scripts to your package.json:

{
"scripts": {
"build": "tsc",
"watch": "tsc --watch",
"lint": "eslint . --ext .ts"
}
}

Configure your editor with TypeScript extensions and set up a linter and code formatter to help you write more efficient and error-free code.

3. Converting a JavaScript Project to TypeScript

Converting a JavaScript project to TypeScript involves more than just changing file extensions. You need to gradually introduce type safety while ensuring the existing code remains functional. For example, start by adding type annotations to your JavaScript functions:

// users.ts
interface User {
id: number;
name: string;
email: string;
}

// Before
const getUser = (id) => {
return db.users.find(user => user.id === id);
};

// After
const getUser = (id: number): User | undefined => {
return db.users.find(user => user.id === id);
};

Make sure to rename your .js files to ts. Then handle type errors and implicit any types, and refactor your code to make the most of TypeScript's features. Finally, enable type checking in your tsconfig.json file:

{
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"strict": true
}
}
```Remember, when using [interfaces](/core-concepts/typescript-interface), start with your most commonly used data structures.
## 4. Using TypeScript with Node.js for Backend Development
TypeScript is a great choice for backend development with [Node.js](https://docs.convex.dev/client/javascript/node), as it provides type safety and helps catch errors early.
Install the necessary dependencies:
```shell
npm init -y
npm install express @types/express typescript ts-node @types/node

Create a new Node.js project with TypeScript and use popular frameworks like Express.js:

// server.ts
import express, { Request, Response } from 'express';

const app = express();

app.get('/', (req: Request, res: Response) => {
res.send('Hello, World!');
});

app.listen(3000, () => {
console.log('Server listening on port 3000');
});

You can also use TypeScript with other Node.js frameworks and libraries, such as TypeORM and NestJS. When using generics, you can create reusable service layers for different data types. For utility types, consider using Partial<T> for update operations. When building with Convex, you'll use TypeScript to define your backend functions with proper typing for queries and mutations.

5. Handling TypeScript Types and Interfaces

Understanding the difference between type aliases and interfaces is crucial in TypeScript. Use types and interfaces to define complex objects and functions, helping you keep your code organized and scalable. For example:

// Interface for defining object shapes
interface User {
id: string;
name: string;
email: string;
settings?: UserSettings; // Optional property
}

// Type for complex combinations
type UserResponse = User | null;
type UserId = string | number;

// Generic interface for API responses
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}

// Using interfaces with classes
interface UserService {
getUser(id: UserId): Promise<UserResponse>;
updateUser(id: UserId, data: Partial<User>): Promise<User>;
}

class UserAPI implements UserService {
async getUser(id: UserId): Promise<UserResponse> {
// Implementation
return null;
}

async updateUser(id: UserId, data: Partial<User>): Promise<User> {
// Implementation
return {} as User;
}
}

You can also use utility types like Partial, Required, and Readonly to transform object types and make your code more flexible. When using interfaces and type assertions, choose interfaces for object shapes you might want to extend later. Use type aliases for unions, intersections, or mapped types. For Convex functions, you'll define interfaces for your database schemas and API payloads. Check out more examples in our custom functions guide.

6. Integrating TypeScript with React for Frontend Development

TypeScript is a great choice for frontend development with React, providing strong typing for props, state, and components. Create a new React project with TypeScript and use popular libraries like React Router:

import React, { useState } from 'react';

interface User {
id: string;
name: string;
email: string;
}

interface Props {
initialUsers: User[];
onUserSelect: (user: User) => void;
}

const UserList: React.FC<Props> = ({ initialUsers, onUserSelect }) => {
const [users, setUsers] = useState<User[]>(initialUsers);
const [selectedId, setSelectedId] = useState<string | null>(null);

const handleSelect = (user: User) => {
setSelectedId(user.id);
onUserSelect(user);
};

return (
<div>
{users.map(user => (
<div
key={user.id}
onClick={() => handleSelect(user)}
className={user.id === selectedId ? 'selected' : ''}
>
{user.name} ({user.email})
</div>
))}
</div>
);
};

You can also use TypeScript with other React libraries and frameworks, such as Redux and React Hooks. Try our useState alternative for more efficient React state management with TypeScript.

7. Debugging TypeScript Code in Visual Studio Code

Debugging TypeScript code in Visual Studio Code is straightforward, thanks to the built-in debugger and the ability to set breakpoints and inspect variables. You can also use the console.log function to log messages and inspect your code:

// debug.ts
function add(a: number, b: number): number {
return a + b;
}

const result = add(2, '3'); // Error: Type 'string' is not assignable to type 'number'.

Add type checking in catch blocks for better error handling:

try {
const result = await fetchData();
} catch (error) {
if (error instanceof TypeError) {
console.error('Type error:', error.message);
} else if (error instanceof NetworkError) {
console.error('Network error:', error.message);
}
}

By using these debugging techniques, you can quickly identify and fix errors in your TypeScript code.

Common Challenges and Solutions

TypeScript beginners often face several specific hurdles. Here's how to overcome them: "Property does not exist on type 'any'" errors:

// Problem:
const data = JSON.parse(jsonString);
console.log(data.user.name); // Error!

// Solution:
interface ApiResponse {
user: {
name: string;
id: number;
}
}
const data = JSON.parse(jsonString) as ApiResponse;
console.log(data.user.name); // Works!

Working with external libraries lacking type definitions:

// Create a declaration file (external-lib.d.ts)
declare module 'external-lib' {
export function someFunction(arg: string): number;
export const someValue: string;
}

Type narrowing in conditional blocks:

type Circle = { kind: 'circle'; radius: number };
type Square = { kind: 'square'; size: number };
type Shape = Circle | Square;

function getArea(shape: Shape) {
if (shape.kind === 'circle') {
// TypeScript knows shape is Circle here
return Math.PI * shape.radius ** 2;
} else {
// TypeScript knows shape is Square here
return shape.size * shape.size;
}
}

Final Thoughts about TypeScript

This guide covered TypeScript fundamentals - from project setup to debugging. Start with small projects, add types gradually, and build on your JavaScript knowledge. For real-world examples and deeper dives into TypeScript, browse our developer blog.