Setting up an Express + Typescript Server with Vue + Vite
#express, #typescript, #node, #vue, #vite, #proxy, #server
Feb 13, 202416 min read

Introduction
Venturing into backend development can feel like stepping into uncharted territory, especially for us frontend developers. As a self-proclaimed "frontend girlie," I'll admit, I felt apprehensive about diving into backend development. However, after offloading some of my project's solely client-side logic to the backend, I discovered that things server-side aren’t as intimidating as I imagined.
If you resonate with my apprehensions about backend development, then you're in the right place. In this blog, I'll guide you through integrating an Express server with TypeScript into your frontend project.
This comprehensive guide is tailored for beginners and will focus on:
- Setting up an Express server with TypeScript
 - Establishing communication between the client and server
 - Configuring Vite to proxy requests
 
Let's dive in and bridge the gap between frontend and backend. 🚀
Getting Started
To access the full source code for this tutorial, visit the GitHub repository here. Feel free to clone, fork, or star the repository for future reference and experimentation. Remember to add the environment variables for the server and client.
Before we start, ensure that you have Node.js installed on your computer. You can check if Node.js is installed by running the command* node -v in your terminal. If it is installed, it will display the version you have. Otherwise, you can install Node.js by clicking here.
Setting up the Server
The first step in setting up our backend is selecting the right tech stack. We'll be using Express.js, a popular Node.js framework known for its simplicity and flexibility. Additionally, we'll leverage TypeScript to bring static typing to our server-side codebase.
Initializing the Project
We’ll begin by creating a new directory and initializing a new Node.js project using npm. Once initialized, we’ll install the necessary dependencies, including Express and TypeScript, by running the respective commands.
- 
Create a New Directory.
We’ll start by creating a new directory for our starter project. You can do this using the terminal or file explorer of your operating system.
mkdir express-starter - 
Navigate to the Project Directory.
Once the directory is created, navigate into it using the
cdcommand.cd express-starter - 
Create Server and Client Folders.
Once inside
express-starterdirectory, create two folders:clientandserverthen navigate into theserverfolder.mkdir server client cd server - 
Initialize a New Node.js Project.
Use
npm initto initialize a new Node.js project. This will generate apackage.jsonfile, which will store metadata about the project and its dependencies. You can either follow the prompts or use the-yflag to accept the default values for all prompts.npm init -y - 
Install Dependencies.
Now, we’ll install the necessary dependencies for our project.
# dependencies npm install express cors dotenv # development dependencies npm install -D typescript @types/cors @types/node @types/express nodemonLets briefly look what each dependency does.
express: The web framework for Node.js that we'll use to build our server.typescript: The TypeScript compiler and language. We want our project to be type safe and catch bugs before runtime.cors: Cross-Origin Resource Sharing to allow cross-origin requests and ensure that our backend APIs can be accessed securely from the client despite running on different ports.dotenv: Loads environment variables from.envfile.nodemon: Nodemon automatically restarts our node application when it detects any changes. This means that we won’t have to stop and restart our app in order for any changes to take effect.@types/node,@types/corsand@types/express: Type definitions for Node.js, Cors and Express to enable TypeScript support.
Dependency vs Development Dependency
We added some dev dependencies during our installations by using the
-Dflag, but why did we need to? Dev dependencies are modules which are only required during development whereas dependencies are required at runtime.Dependencies
Dependencies are essential packages needed for our application to function properly. When we run
npm install, these packages are installed. They're listed in thedependenciessection of thepackage.jsonfile. Without them, our app won't work when deployed.Dev Dependencies
DevDependencies, on the other hand, are only necessary for development and testing purposes. They're not required for the app to run normally, but they're crucial for tasks like building, testing, and linting the code. These are specified in the
devDependenciessection ofpackage.json. - 
Generate a
tsconfig.jsonCreate a
tsconfig.jsonfile to configure TypeScript. This file specifies how TypeScript should compile our code. Runnpx tsconfig.jsonthen selectNode. This command generates atsconfig.jsonfile with some default settings.npx tsconfig.json - 
Create Source Files and Update package.json
Next, create a
srcdirectory. Within thesrcdirectory, we'll create our main file,main.ts# create src folder mkdir src # go into the folder cd src # create .ts file touch main.ts # exit src and return to server directory cd ..Update the entry point in
package.jsonto usemain.jsinstead ofindex.js."main": "main.js", - 
Create a
.envFile.We’ll add a
.envfile to store the environment variables for configuration (e.g. API keys). This file should be kept out of version control to prevent sensitive information from being exposed. Create a.envfile in the root of theserverfolder.touch .env - 
Create a
.gitignoreFile.We’ll add a
.gitignoreto specify which files and directories should be ignored by version control to avoid committing unnecessary files. Create a.gitignorefile in the root of theserverfolder. For now we’ll addnode_modulesand.env*. The*tells Git to ignore any file or folder that starts with.env..env* node_modules/At this point your project structure might look something like this:
express-starter/ ├── client/ └── server/ ├── node_modules/ └── src/ └── main.ts ├── .env ├── .gitignore ├── package.json ├── tsconfig.jsonNow that we’ve done the basic config, let’s create our server.
 
Creating the Server
Inside the src/main.ts file. Add the following snippet.
// server/src/main.ts
import cors from 'cors';
import 'dotenv/config';
import express from 'express';
const app = express();
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
const PORT = process.env.PORT || 3001;
app.get('/api', (_req, res) => {
  res.status(200).json({ message: 'Hello from the server!' });
});
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});
This is a basic express server. This app starts a server and listens on the specified port for connections. The app responds with “Hello from the server!” for requests to the /api route. Let’s understand what the code does.
- Middleware Setup
 
app.use() is how we register middlewares. Middlewares are special functions that the server runs before handling any specific request. They work behind the scenes, between the moment the server receives a request and when it sends back a response to the client.
In our case, we've registered a few global middlewares:
- cors: This middleware allows our server to accept requests from different sources/origins.
 - express.json(): Parses incoming requests as JSON.
 - express.urlencoded({ extended: true }): Parses form data in requests.
 
- Route Setup
 
app.get('/api') sets up a route handler for GET requests specifically to the /api URL. When a GET request is made to this route, the server responds with a status code of 200 and the message "Hello from the server!".
- Server Initialization
 
app.listen() starts the Express app by listening on the specified port (PORT). If no port is specified in the environment variables, it defaults to port 3001.
With this setup, we are a step closer to running our server.
Configuring Nodemon with TypeScript
To execute our main.ts we can run node src/main.ts within the server directory, however we’ll eventually get an error saying Unknown file extension ".ts” .
Node and TypeScript
When you run node src/main.ts, you might expect Node.js to execute the TypeScript file directly. However, Node.js doesn't understand TypeScript natively; it only understands JavaScript.
TypeScript is a superset of JavaScript, therefore our TypeScript code needs to be compiled into JavaScript before it can be executed by Node.js. This compilation process transforms the .ts files into equivalent .js files that Node can understand.
To run our server code, we first need to compile our TypeScript code into JavaScript using the TypeScript compiler (tsc). This will generate a dist/main.js file, which contains the compiled JavaScript code.
# compile all TypeScript files to JavaScript
npx tsc
# run the generated JavaScript file
node dist/main.js
In the commands above, npx tsc invokes the TypeScript compiler (tsc) to compile all TypeScript files in the project into JavaScript. The resulting JavaScript files are output to the dist directory. We can run the generated JavaScript file using Node.js.
The output directory for compiled JavaScript files is specified in the tsconfig.json file using the outDir property.
// server/tsconfig.json
{
  "compilerOptions": {
    "outDir": "./dist"
  }
}
Finally, we need to update our .gitignore file to include the dist folder. This ensures that the compiled JavaScript files are not included in version control.
// server/.gitignore
node_modules
dist
.env*
Detecting Changes with Nodemon
If we make changes to any TypeScript files, we'll have to manually compile and run those files again with npx tsc and node dist/main.js. This repetition quickly become tedious. To streamline this process, we can use a tool called nodemon.
Nodemon is a tool that helps develop Node.js based applications by automatically restarting the node application when file changes in the directory are detected. This saves us the hassle of manually stopping and restarting the server every time we make changes to our code.
We already installed nodemon, so now we just need to configure it.
Configuring Nodemon
To configure nodemon we can add a nodemonConfig to our package.json.
// server/package.json
"scripts": {
    "dev" : "nodemon"
  },
  "nodemonConfig":{
     "watch": [
      "src"
    ],
    "exec": "tsc && node ./dist/main.js",
    "ext": "ts,js,json"
  },
The nodemonConfig section tells nodemon how to behave when monitoring file changes:
watch: Instructs nodemon to watch for any changes in thesrcfolder.ext: Specifies the file extensions to watch for changes (ts,js,json).exec: Defines the command to run when changes are detected. In this case, it runstscto compile TypeScript files and then executesnode ./dist/main.jsto start the server.
Now, we can simply run npm run dev to start the server with nodemon, automating the process of monitoring file changes and restarting the server accordingly. With these changes our server is ready to receive requests. If you have an API platform like Postman you can try it by sending a GET request to http://localhost:3001/api, or just visit the link in your browser.
Setting up the Client
Our server is ready to accept requests from the client. On the frontend we’ll use Vue. Vue uses a build setup based on Vite, which we’ll configure to communicate with our server. Let’s get started.
- 
Enter the Client Directory
First we’ll stop and exit the
serverdirectory and move into theclient. Press^ + C(control + C) in the terminal where the server is running then run the following command.# exit server directory and enter client cd ../client - 
Create a Vue Project
To create a Vue app, run the following command. This will create our project directly in the
clientfolder as specified by the..npm create vue@latest .You will be prompted to make several choices, such as package name and TypeScript support. For our project, name it
express-vueand choose "yes" for TypeScript support, while selecting "no" for other optional features to keep the project simple.Next, install the dependencies and start the development server.
# Install dependencies npm install # Start the development server npm run devYou should now have your Vue project running at http://localhost:5173/.
 - 
Add a
.envfileAdd a
.envin the root of theclientfolder with the following variables.VITE_SERVER_URL=http://localhost:3001 VITE_SERVER_API_PATH=/apiPlease note that changes made to the
.envfile may require the server to be restarted. To stop the server, press^ + C(control + C) in the terminal where the server is running. Then, to restart the server, runnpm run devagain. - 
Update
vite.config.tsWe want to customize our
vite.config.tsfile to enable communication between our client and server. ThedefineConfigis a helper function that used to define configuration options in a Vite project. defineConfig can take either an object or a function as an argument. We’ll pass it a function so we can load our environment variables.import { fileURLToPath, URL } from 'node:url'; import vue from '@vitejs/plugin-vue'; import { defineConfig, loadEnv } from 'vite'; // <https://vitejs.dev/config/> export default defineConfig((env) => { const envars = loadEnv(env.mode, './'); const serverURL = new URL( envars.VITE_SERVER_URL ?? '<http://localhost:3001>' ); const serverAPIPath = envars.VITE_SERVER_API_PATH ?? '/api'; return { envDir: './', // make the API path globally available in the client define: { __API_PATH__: JSON.stringify(serverAPIPath), }, plugins: [vue()], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)), }, }, server: { port: 5173, proxy: { // proxy requests with the API path to the server // <http://localhost:5173/api> -> <http://localhost:3001/api> [serverAPIPath]: serverURL.origin, }, }, }; });Let's break down what each part of this configuration does.
- Loading Environment Variables: The 
loadEnvfunction is used to load environment variables based on the current mode (e.g., development, production). It reads the.envfiles in the project directory and loads variables into theenvarsobject. - Parsing Server URL and API Path: The server URL and API path are parsed and default values are provided if they are not found in the 
.envfile. - Configuration Options:
envDir: Specifies the directory where environment variables are located.define: Allows defining global constants that will be replaced during the build process. In this case,__API_PATH__is defined with the value of the server API path.plugins: Specifies the Vite plugins used in the project. Here, the Vue.js plugin (vue()) is added.resolve.alias: Defines aliases for module resolution. In this case, the@alias is set to thesrcdirectory.
 - Server Configuration:
server.port: Specifies the port on which the Vite development server will run. Here, it's set to5173.server.proxy: The proxy settings enable communication between the client and the server. Requests matching the API path are forwarded to the server URL. When we send a request tohttp://localhost:5173/apiit will be forwarded to our server athttp://localhost:3001/api.
 
 - Loading Environment Variables: The 
 - 
Declaring Global Constant
After adding a global constant in our
vite.config.tsfile, we need TypeScript to be aware of this constants for type checking. To achieve this, we declare the type definitions in theenv.d.tsfile.// client/env.d.ts /// <reference types="vite/client" /> declare const __API_PATH__: string;In this declaration, we inform TypeScript about the existence of the
__API_PATH__constant and specify its type as a string. This ensures that TypeScript provides type checking and IntelliSense support for this global constant throughout our project. - 
Sending Requests to the Server
Now that we've configured our Vite project to communicate with the server, let's update the
App.vuefile to send a request and display the response from the server. The following snippet demonstrates how to fetch data from the server using Vue.js composition API and display it in the app.// client/src/App.vue <script setup lang="ts"> import { ref } from "vue"; // Global constant containing the API base URL -> /api const baseURL = __API_PATH__; // Reactive variables for managing loading state and response message const isLoading = ref(false); const message = ref(""); // Function to fetch data from the server async function fetchAPI() { try { // Set loading state to true isLoading.value = true; // Send a GET request to the server const response = await fetch(baseURL); // Parse the JSON response const data = await response.json(); // Update the message with the response data message.value = data.message; } catch (error) { // Handle errors message.value = "Error fetching data"; console.error(error); } finally { // Reset loading state isLoading.value = false; } } </script> <template> <!-- Button to trigger the fetchAPI function --> <button @click="fetchAPI">Fetch</button> <!-- Display loading message while fetching data --> <p v-if="isLoading">Loading...</p> <!-- Display the response message if available --> <p v-else-if="message">{{ message }}</p> </template>In this code snippet:
- We import the 
reffunction from Vue's composition API to create reactive variables for managing the loading state (isLoading) and the response message (message). - The 
fetchAPIfunction is defined to send a GET request to the server using thefetchAPI. - While the request is being processed, the loading state is set to 
true, and a loading message is displayed. - Once the request is complete, the loading state is reset, and the response message from the server is displayed.
 - Any errors that occur during the request are caught and 
messageis updated. 
With these updates, our Vue application is now capable of fetching data from the server and displaying it to the user.
 - We import the 
 - 
Start both servers.
Since our client is already running, we need to start our server. If you're using VS Code, you can open a new terminal by right-clicking the Terminal option in the menu bar and selecting New Terminal. Alternatively, you can open a new terminal window and navigate to the project directory. Once you're in the project directory, start the server by running the following commands:
cd server npm run devThis will navigate to the
serverdirectory and start the server in development mode. - 
Initiating the Client-Side Request
The final step is to trigger the request from the client by clicking the button in
App.vue. This will send a request to our sever. 
Conclusion
Congratulations! 🎉 You've successfully learned how to set up a full-stack web development environment using Express.js and TypeScript for the backend and Vue.js with Vite for the frontend.
Throughout this guide, we've covered:
- Setting up the Backend: We started by initializing an Express.js server with TypeScript, configuring middleware, handling routes, and compiling our code to be compatible for execution by Node.js.
 - Configuring the Frontend: Next, we configured Vite to allow communication between the client and server. We leveraged Vite’s server options to proxy specific requests to the server.
 - Client-Server Communication: With our environment set up, we learned how to send requests from the frontend to the backend using Vue.js composition API. By fetching data from the server and updating the UI, we created a responsive web application.
 
I hope this tutorial lessened your apprehensions towards working with server-side logic now that you've laid the foundation for building dynamic web applications. So keep experimenting, keep building, and don't hesitate to delve deeper into the full-stack side of things.
Happy coding!

Back to Blogs