We've been working hard at making Payload + TypeScript a match made in heaven, and over the last few months we've released a suite of features, including automatic type generation, which makes Payload by far the best TypeScript headless CMS available. To celebrate, we're going to show you how to scaffold a TypeScript and Express project from scratch, including testing it with Jest and debugging with VSCode.
By understanding just a few new concepts, you can master your dev environment's setup to maximize productivity and gain a deep understanding how it all works together. Let's get started.
Before going further, make sure you have the following software:
Create a new folder,
cd into it, and initialize:
We'll need a few baseline dependencies:
dotenv- to set up our environment easily
express- Payload is built on top of Express
ts-node- to execute our TypeScript project in development mode
typescript- base TS dependency
payload- no description necessary
nodemon- to make sure our project restarts automatically when files change
Install these dependencies by running:
and the rest as devDependencies using:
In the root of your project folder, create a new file called
tsconfig.json and add the following content to it. This file tells the TS compiler how to behave, including where to write its output files.
In the above example config, we're planning to keep all of our TypeScript files in
/src, and then build to
We'll be using
dotenv to manage our environment and get ourselves set up for deployment to various different environments like staging and production later down the road. The
dotenv package will read all values in a
.env file within our project root and bind their values to
process.env so that you can access them in your code.
Let's create a
.env file in the root folder of your project and add the following:
Make sure that the
MONGO_URL line in your
.env matches an available MongoDB instance. If you have Mongo running locally on your computer, the line above should work right out of the box, but if you want to use a hosted MongoDB like Mongo Atlas, make sure you copy and paste the connection string from your database and update your
For more information on what these values do, take a look at Payload's Getting Started docs.
Setting up an Express server might be pretty familiar. Create a
src/server.ts file in your project and add the following to the file:
The file above first imports our server dependencies. Then, we use
dotenv to load our
.env file at our project root. Next, we initialize Payload by providing it with our secret key, Mongo connection string, and Express app. Finally, we tell our Express app to listen on the port defined in our
We'll use Nodemon to automatically restart our server when any
.ts files change within our
./src directory. Nodemon will execute
ts-node for us, which will use our server as its entry point. Create a
nodemon.json file within the root of your project and add the following content.
The Payload config is central to everything Payload does. Add it to your
src folder and enter the following baseline code:
This config is very basic - but check out the Config docs for more on what the Payload config can do. Out of the box, this config will give you a default
Users collection, a simple
Posts collection with a few fields, and will open up the admin panel to you at
The config also specifies where Payload should output its auto-generated TypeScript types which is super cool (we'll come back to this).
The last step before we can fire up our project is to add some development, build, and production NPM scripts.
package.json and update the
scripts property to the following:
To support Windows environments consider adding the
cross-env package as a devDependency and use it in scripts before setting variables.
The first script uses Payload's
generate:types command in order to automatically generate TypeScript types for each of your collections and globals automatically. You can run this command whenever you need to regenerate your types, and then you can use these types in your Payload code directly.
The next script is to execute
nodemon, which will read the
nodemon.json config that we've written and execute our
/src/server.ts script, which fires up Payload in development mode.
The following three scripts are how we will prepare Payload's admin panel for production as well as how to compile the server's TypeScript code into regular JS to use in production.
Finally, we have a
serve script which is used to serve our app in production mode once it's built.
We're ready to go! Run
yarn dev in the root of your folder to start up Payload. Then, visit
http://localhost:3000/admin to create your first user and sign into the admin panel.
Now that we've got a server generated, let's try and generate some types. Run the following command to automatically generate a file that contains an interface for the default
Users collection and our simple
Then check out the file that was created at
/src/generated-types.ts. You can import these types in your own code to do some pretty awesome stuff.
Now it's time to get some tests written. There are a ton of different approaches to testing, but we're going to go straight for end-to-end tests. We'll use Jest to write our tests, and set up a fully functional Express server before we start our tests so that we can test against something that's as close to production as possible.
We'll also use
mongodb-memory-server to connect to for all tests, so that we don't have to clutter up our development database with testing documents. This is great, because our tests will be totally controlled and isolated, but coverage will be incredibly thorough due to how we'll be testing the full API from top to bottom.
Payload will automatically attempt to use
mongodb-memory-server if two conditions are met:
NODE_ENVis equal to
OK. Let's install all the testing dependencies we'll need:
Now let's add two new config files. First,
We use Babel so we can write tests in TypeScript, and test full React components.
A sharp eye might find that we're using a
globalSetup file in
jest.config.js to scaffold our project before any of the real magic starts. Let's add that file:
In this file, we're performing the following actions before our tests are executed:
You'll notice we are importing
testCredentials from next to our
globalSetup file. Because our Payload API will require authentication for many of our tests, and we're creating that user in our
globalSetup file, we will want to reuse our user credentials in other tests to ensure we can authenticate as the newly created user. Let's create a reusable file to store our user's credentials:
Now that we've got our global setup in place, we can write our first test.
Add a file called
The test above is written in TypeScript and imports our auto-generated
User TypeScript interface to properly type the
fetch response that is returned from Payload's
login REST API.
It will expect that a token is returned from the response.
The last step is to add a script to execute our tests. Let's add a new line to our
Now, we can run
yarn test to see a successful test!
Debugging can be an absolutely invaluable tool to developers working on anything more complex than a simple app. It can be difficult to understand how to set up, but once you have it configured properly, a proper debugging workflow can be significantly more powerful than just relying on
console.log all the time.
You can debug your application itself, and you can even debug your tests to troubleshoot any tests that might fail in the future. Let's see how to set up VSCode to debug our new Typescript app and its tests.
First, create a new folder within your project root called
.vscode. Then, add a
launch.json within that folder, containing the following configuration:
The file above includes two configurations. The first is to be able to debug your Express + Payload app itself, including any files you've imported within your Payload config. The second debug config is to be able to set breakpoints and debug directly within your Jest testing suite.
To debug, you can set breakpoints right in your code by clicking to the left of the line numbers. A breakpoint will be set and show as a red circle. When VSCode executes your scripts, it will pause at the breakpoint(s) you set and allow you to inspect the value of all variables, where your function(s) have been called from, and much more.
To start the debugger, click the "Run and Debug" sidebar icon in VSCode, choose the debugger you want to start, and click the "Play" button. If you've placed breakpoints, VSCode will automatically pause when it reaches your breakpoint.
Here is an example of a breakpoint being hit within our
Here is a screenshot of a breakpoint being hit within our
Debugging can be an invaluable tool to you as a developer. Setting it up early in your project will pay dividends over time as your project gets more complex, and it will help you understand how your project works to an extremely fine degree.
With all of the above pieces in place, you have a modern and well-equipped dev environment that you can use to build out Payload into anything you can think of - be it an API to power a web app, native app, or just a headless CMS to power a website.
You can find the code for this guide here. Let us know what you think!
If you haven't already, stop by GitHub and give us a star by clicking on the "Star" button in the top-right corner of our repo. With our inclusion into YC and our move to open-source, we're looking to dramatically expand our community and we can't do it without you.
We've recently started a Discord community for the Payload community to interact in realtime. Often, Discord will be the first to hear about announcements like this move to open-source, and it can prove to be a great resource if you need help building with Payload. Click here to join!