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 easilyexpress
- Payload is built on top of Expressts-node
- to execute our TypeScript project in development modetypescript
- base TS dependencypayload
- no description necessarynodemon
- to make sure our project restarts automatically when files changeInstall 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.
Example tsconfig.json
:
In the above example config, we're planning to keep all of our TypeScript files in /src
, and then build to /dist
.
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 .env
accordingly.
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 .env
file.
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.
Example nodemon.json
:
The Payload config is central to everything Payload does. Add it to your src
folder and enter the following baseline code:
./src/payload.config.ts
:
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 http://localhost:3000/admin
.
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.
Open your 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 Posts
collection:
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_ENV
is equal to test
OK. Let's install all the testing dependencies we'll need:
Now let's add two new config files. First, babel.config.js
:
We use Babel so we can write tests in TypeScript, and test full React components.
Next, jest.config.js
:
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:
src/tests/globalSetup.ts
:
In this file, we're performing the following actions before our tests are executed:
mongodb-memory-server
.env
fileYou'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:
src/tests/credentials.ts
:
Now that we've got our global setup in place, we can write our first test.
Add a file called src/tests/login.spec.ts
:
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 package.json
scripts
property:
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:
./.vscode/launch.json
:
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 src/server.ts
file:
Here is a screenshot of a breakpoint being hit within our login.spec.ts
test:
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!