express-apollo-prisma starter kit
This starter kit features Express, Typescript API setup
Table of Contents
- express-apollo-prisma starter kit
Overview
Tech Stack
Included Tooling
- Jest - Test runner
- TypeScript - Type checking
- ESLint - Code linting
- Prettier - Code formatting
Installation
CLI (Recommended)
npm create @this-dot/starter --kit express-apollo-prisma
or
yarn create @this-dot/starter --kit express-apollo-prisma
- Follow the prompts to select the
express-apollo-prisma
starter kit and name your new project. cd
into your project directory and runnpm install
.- Make sure you have docker & docker-compose installed on your machine
- Create a
.env
file and copy the contents of.env.example
into it. - Run
npm run infrastructure:start
to start the database and the Redis instances - Run
npm run start
to start the development server. - Open your browser to
http://localhost:4001
to see the API documentation with the existing endpoints.
Manual
git clone https://github.com/thisdot/starter.dev.git
- Copy and rename the
starters/express-apollo-prisma
directory to the name of your new project. - Make sure you have docker & docker-compose installed on your machine
cd
into your project directory and runnpm install
.- Make sure you have docker & docker-compose installed on your machine
- Create a
.env
file and copy the contents of.env.example
into it. - Run
npm run infrastructure:start
to start the database and the Redis instances - Run
npm run start
to start the development server. - Open your browser to
http://localhost:4001
to see the API documentation with the existing endpoints.
Commands
npm run infrastructure:start
- Starts up a Mysql database and Redis instance for cachingnpm run infrastructure:stop
- Stops the running database and Redis docker containers.npm run db:seed
- Allows you to seed the database (See the Seeding section)npm run dev
- Starts the development server (Needs a running infrastructure first)npm run build
- Builds the app.npm start
- Starts the built app. (Needs a running infrastructure first)npm test
- Runs the unit tests.npm run lint
- Runs ESLint on the project.npm run format
- Formats code for the entire project.npm prisma:format
- Updates your database using migrations during development and creates the database if it does not exist.npm prisma:migrate:reset
- Deletes and recreates the database, or performs a ‘soft reset’ by removing all data, tables, indexes, and other artifacts.npm prisma:migrate:dev
- Updates your database using migrations during development and creates the database if it does not exist.npm run prisma:generate
- Generates the API schema types into thesrc/interfaces/schema.ts
filenpm run prisma:deploy
- Applies all pending migrations, and creates the database if it does not exist. Primarily used in non-development environments.npm run prisma:studio
- Starts a GUI that allows for easy exploration and manipulation of data.
Database and Redis
To start up your API in dev mode with an active database connection, please follow the following steps:
- create a
.env
file. For the defaults, copy the contents of the.env.example
file’s content into it. - run
npm run infrastructure:start
- run
npm run dev
The above steps will make sure your API connects to the database and Redis instances that get started up with docker. When you finish work, run npm run infrastructure:stop
to stop your MySQL and Redis containers.
Seeding
To seed the database, you need to do the following steps:
- create a
.env
file. For the defaults, copy the contents of the.env.example
file’s content into it. - run
npm run infrastructure:start
- run
npm run db:seed
- run
npm run prisma:studio
to view your database.
Updating Schemas and Entities
As your application grows you need to add/update your entities.
For example, let’s edit your Technology
entity.
-
Edit your
prisma/schema.prisma
file and add anauthorName
property to your entitymodel TechnologyEntity { authorName String? description String? displayName String @unique id Int @id @default(autoincrement()) url String? @@map("technology") }
-
Run
npm run prisma:migrate:dev
to generate a migration on Prisma. Then add your migration name.This will generate a migration folder under
prisma/migrations
. That alters our database schema. -
Update your GraphQL type definitions for the
Technology
entitysrc/graphql/schema/technology/technology.typedefs.ts
with the newauthorName
property.import gql from 'graphql-tag'; export const technologyTypeDefs = gql` """ Technology object """ type Technology { "The ID of the Technology" id: ID! "The name of the Technology" displayName: String! "A brief description of the Technology" description: String "The link to the Technology's documentation" url: String "The author of the technology" authorName: String } type Query { "Returns a single Technology by ID" technology(id: ID!): Technology "Returns a list of Technologies" technologies: [Technology]! } input CreateTechnology { "Technology Name" displayName: String! "A brief description of the Technology" description: String "The link to the Technology's documentation" url: String "The author of the technology" authorName: String } input UpdateTechnology { "Technology Name" displayName: String "A brief description of the Technology" description: String "The link to the Technology's documentation" url: String "The author of the technology" authorName: String } """ Technology mutations """ type Mutation { "Creates a new Technology" createTechnology(input: CreateTechnology!): Technology! "Updates a Technology" updateTechnology(id: ID!, input: UpdateTechnology!): Technology! "Removes a Technology" deleteTechnology(id: ID!): Boolean } `;
You can download the Apollo GraphQL extension that adds syntax highlighting for GraphQL files and gql templates inside JavaScript files.
-
Update our automatically generated code by running
graphql-codegen
. This will generate a newsrc/graphql/schema/index.ts
file.npm run codegen
-
Finally, update your resolvers with updated properties you’d like to be returned by GraphQL.
Production build
The npm run build
command compiles the TypeScript code into the /dist
folder and generates a package.json
file. To use it in production, for example in a Docker container, one would copy the contents of the /dist
folder, and then run npm install
to have all the dependencies.
CORS Cross-Origin Resource Sharing
The Cross-Origin Resource Sharing standard works by adding new HTTP headers that let servers describe which origins are permitted to read that information from a web browser. For security reasons, browsers restrict cross-origin HTTP requests initiated from scripts. This means that you cannot request data from web applications on ’https://domain-a.com’ from ’https://domain-b.com/data.json‘.
This application accepts CORS from all origins by default. Some web applications may require you to add the HTTP header 'Access-Control-Allow-Origin': '*'
to allow access.
In order to restrict origin URLs that can access your API, you need to add a list of comma-separated origin URLs in the CORS_ALLOWED_ORIGINS
variable located in your .env
file. For example CORS_ALLOWED_ORIGINS="https://starter.dev"
. In case you need to access the API in a development environment i.e. a Sveltekit application, you can add the local URL http://127.0.0.1
to the CORS_ALLOWED_ORIGINS
variable as CORS_ALLOWED_ORIGINS=https://starter.dev,http://127.0.0.1
.
Project Structure
Folder structure
- **/prisma/** - holds prisma migration files and schema.
- **/src
- /graphql - holds graphql-related files.
- /data-sources/** - holds a Datasource file for each model.
- /mappers/** - holds a mappers for particular models.
- /schema/** - holds a directory for each GraphQL Module. Each module needs a `resolver`, `typedef` and optionally a test.
- /server-context/** - holds server-context types and middleware
- /utils/** - holds related utilities required in graphql connection
- /redis/** - holds redis connection files
- main.ts - bootstraps Express application with Apollo Server
GraphQL Moodules
This pattern follows the single responsibility principle since each file has one purpose. For example, the .resolvers.ts files handle data for all resolvers with the functionality related to data fetching for your query. The .spec.ts files handle all the unit tests for the resolvers. The .typedefs.ts files handle all the types for GraphQL.
Example GraphQL Module
technologies.resolvers.ts
- Resolvers for the Technology entity.technologies.spec.ts
- Unit tests for the Technology entity.technologies.typedefs.ts
- Type definitions for the Technology entity.
Technologies
Express
The ExpressJS API starts at the main.ts
file. The bootstrapApp()
method uses the expressMiddleware
function from @apollo/server
to create Graphql endpoints.
Apollo Server
Apollo Server is an open-source, spec-compliant GraphQL server that’s compatible with any GraphQL client, including Apollo Client. It’s the best way to build a production-ready, self-documenting GraphQL API that can use data from any source.
We use the expressMiddleware
function from @apollo/server
to enable you to attach Apollo Server to an Express server. We also recommend using ApolloServerPluginDrainHttpServer
plugin to ensure your server gracefully shuts down.
The data sources are located in src/graphql/data-sources
. The data sources of the entities are passed in src\graphql\server-context\server-context-middleware-options.ts
.
Prisma
Prisma is a next-generation ORM that makes working with databases easy for application developers and features.
We use Prisma for the following:
- Prisma Client - an auto-generated and type-safe database client for use in your application.
- Prisma Migrate - a declarative data modeling and migration tool.
- Prisma Studio - the easiest way to explore and manipulate your data in all of your Prisma projects.
To learn more about Prisma
RabitMQ
RabbitMQ is an open-source message broker that allows multiple applications to communicate with each other through queues. It’s a powerful tool for handling tasks asynchronously and distributing workloads across multiple machines.
RabbitMQ offers several benefits, such as:
- Scalability: It can handle large volumes of messages and distribute workloads across multiple machines.
- Reliability: messages are stored in a durable queue, ensuring that they are not lost in the event of a system failure.
- Flexibility: It supports multiple messaging protocols, including AMQP, STOMP, and MQTT, allowing it to integrate with a wide range of systems and applications.
- Extensibility: It is highly customizable and can be extended with plugins and custom message-processing logic.
The kit provides an implementation of queueing using RabbitMQ, the most widely deployed open-source message broker that allows multiple applications to communicate with each other through queues.
To start the worker that processes messages in the queue, run the command:
npm run infrastructure:start
- starts the RabbitMQ server (you can skip this if you already ran this command)npm run queue:run
- starts the queue worker
This should start a process that listens for messages in our queue and processes them, see the queue/worker.ts
file to modify it to your needs:
// Listener
channel.consume(AMQP_QUEUE_JOB, (message) => {
// process queue message here
});
The src/queue/job-generator-handler.ts
file contains the logic for generating a job and adding it to the queue, the createJobGeneratorHandler
function creates an Express request handler that accepts a message and adds it to the queue. The createQueueChannel
function sets up a connection to the RabbitMQ server and returns a channel object, which is used to perform various actions on the queue, such as creating a new queue, binding it to an exchange, and publishing a message to the queue.
To use this implementation of queueing, you can send a POST
request to the /example-job
endpoint with a message
in the request body, and the message will be added to the queue. Once the message is in the queue, it will be processed in the order it was added
curl -X POST http://localhost:4001/example-job
-H "Content-Type: application/json"
-d '{"message": "simple queue message!"}'
Caching
To reduce API response times and rate limiting, you can cache your data so that the application makes a single request to an API, and all the subsequent data requests will retrieve the data from the cache. We use Redis, an in-memory database that stores data in the server memory, to counter our response problems.
We set up Redis by creating a Redis client with the createClient
function from the redis
package. Each entity has optional caching. This can be achieved by passing the Redis client with the TTL(time to live) in the src\graphql\server-context\server-context-middleware-options.ts
.
Testing
Testing is set up with Jest. You can see some example spec files under src/graphql/schema/technology
.
Deployment
To deploy this starter kit to production, you will need to choose a cloud provider or hosting service, such as AWS, Google Cloud Platform, Heroku, or DigitalOcean, to host your application. The exact deployment steps will depend on your chosen provider or service, but generally, the following steps will be involved:
-
Prepare your application for deployment by running any necessary build or compile steps. In this case, you can run the
build
script by runningnpm run build
which will transpile TypeScript code to JavaScript.npm run build
-
Create a production-ready database, cache and queueing infrastructure, by running
npm run infrastructure:start
-
Deploy your application to your chosen provider or service using their deployment tools or services. You can use the start script to start your application in production mode. You may also need to configure any necessary proxy or routing rules to direct incoming traffic to your application.
-
Monitor your application for any issues or errors and adjust your deployment as needed. This may involve configuring load balancers, auto-scaling, or other performance optimization features, depending on your chosen provider or service.