Skip to main content

3 posts tagged with "express"

View All Tags

· 4 min read
Mitch Chimwemwe Chanza

Throughout this series, we will construct a comprehensive backend utilizing contemporary tools and cutting-edge concepts. My earnest desire is for you to accompany this journey from its inception, following the episodes in the order of their publication. This sequential approach is designed to facilitate a thorough understanding of the foundational concepts, making it particularly beneficial for beginners. However, if you happen to be an expert in the field, feel free to leverage your skills and customize the process to align with your specific objectives. Whether you're just starting out or a seasoned professional, there's something valuable for everyone in this series as we delve into the intricacies of backend development.

Please note that there will be coresponding youtube videos for each topic we cover in this series

Project Setup

Let's initiate the project setup and push it to GitHub. In this endeavor, we will be employing various frameworks, libraries, and programming languages. The selection of tools is independent of the project structure. Below is a list of tools we will be utilizing:

As the project progresses, additional tools may be incorporated into this list.

Create a Monorepo

Assuming PNPM is installed (for installation instructions, refer to pnpm), let's create a monorepo:

mkdir api-monorepo
cd api-monorepo
# Create an API folder
mkdir api
# Initialize PNPM project
pnpm init
# Initialize PNPM monorepo configuration
touch pnpm-workspace.yaml
# Add the following in the workspace file
packages:
- "api"
- "frontend" # This is optional for now
# setup api project
cd api
# initialize project
pnpm init
# install initial dependencies
pnpm add -D typescript @types/node
# initialize typescript
npx tsc --init
# modify tsconfig.json to suit your development requirements. you might want to enable more features in that file.


With these steps completed, we are prepared to weave together the components of our project. This marks the beginning of our successful project journey.

Dive into Part 2 of this series.

It is advisable to commit our modifications and push them to the GitHub repository. This precaution ensures that, in the event of any issues with our local version, we can easily retrieve the latest version from the remote repository. For further insights on GitHub and its functionalities please refer to github documementation .

The entirety of this project is available to the public on Github, where you can find all the settings and components featured in the series. Each part of the series corresponds to a branch within the repository. To access the completed project, simply navigate to the main branch.

· 5 min read
Mitch Chimwemwe Chanza
note

Refer to the earlier post - part 1 , to familiarize yourself with the configuration outlined there. This will enable you to continue with the instructions provided in this guide.

Quick Overview

in this part we are going to implement the following:

  • Install additional dependencies
  • Additional project configurations
  • Setup minimal server using express
  • Test the server

Initial Server configuration

# create a src folder inside api folder
mkdir -p api/src
#create main typescript file
touch api/src/main.ts
# lets change directory to api
cd api
# install additional api dependencies
pnpm add cors express dotenv
# this will install three dependencies in devdependencies
pnpm add -D @types/{node,cors,express} nodemon

info

Important: Additional configuration

It's worth mentioning that when using pnpm, we have the ability to set filters in order to run scripts globally in the project. Let's go ahead and set up some global scripts with filters. We also need to trim the typescript compile configuration file tsconfig.json.

{
"scripts" {
"api":"pnpm --filter api"
}
// ... rest of the configuration
}

Adding additional settings to our project is optional but can help reduce clutter in your script. Specifically, we need to configure nodemon to run .ts files without waiting for compilation. This can be achieved by using the ts-node module. Let's take a look at the configuration.

# we need first to install ts-node
pnpm api add -D ts-node

Following the installation of ts-node, the next step involves configuring nodemon to operate in conjunction with ts-node. To achieve this, it is necessary to establish a nodemon configuration file, nodemon.json, located at the root of our project. Additionally, we must modify our dev script to execute nodemon while adhering to the specified configuration.

{
"watch": ["src","graphql","hasura","prisma,tsconfig.json,package.json,.env"],
"ext": "ts,graphql,json,prisma,js,env",
"ignore": ["**/*.spec.ts","**/*.spec.graphql","**/*.spec.json","**/*.spec.js"],
"execMap": {
"ts": "ts-node ./src/main.ts"
},

"exec": "ts-node ./src/main.ts"
}

Now that we have prepared the environment for our server to run smoothly, it's time to set up the server and test it. we need to create two files, the main entery file for our api api/src/main.ts and environmental variables file api/.env

import express, { json } from "express";
import cors from "cors";
import dotenv from "dotenv";
dotenv.config();
const env = process.env;
const app = express();
// Initialse base router
const router = express.Router();
// Set initial route
router.get("/", (_req, res) => {
res.send({ message: "Monorepo API Configured!", success: true});
});
// Set v1/api endpoint
app.use("/v1/api", router);
// configure cors. the delimeter here can be anything that you have used in your .env file. for my example here am using comma to separate the urls.
app.use(cors({ origin: env.ALLOWED_ORIGINS?.split(",") }));
// enable json serialization
app.use(json());
// start server
app.listen(env.PORT ? +env.PORT : 9000, () => {
console.log(`Server started on http://localhost:${env.PORT}/v1/api`);
})

Lets run the server now.

pnpm api dev
# our server shpuld be up and running on http://localhost:9100 or http://localhost:9000 depending on the PORT set in .env file

Am glad you have made it this far. we have more to cover in this series. proceed to Part 3 to get started.

· 5 min read
Mitch Chimwemwe Chanza
note

This post is a follow-up to the previous one at Part 2: Initiating Server. If you're looking to integrate GraphQL into an existing REST API, this guide is tailored for you. For those aiming to incorporate GraphQL into a new REST API, please refer to the instructions provided in the earlier posts.

I'm delighted that you've reached this point, and I hope you're finding the journey enjoyable. As we continue with this series, there's still much more ground to cover. Now, let's shift our focus to the modernization of the API. I understand if you've been pondering, "What's considered modern about this server?" especially since it relies on the trusted 'express.' Take a moment to relax because you're about to delve into the contemporary aspects of this series.

we are going to cover the following topcs in this guide:

  • Install additional dependencies - GraphQl Tools, Apollo Server 4
  • Reconfigure the API to have separate routes, one for Graphql and one for REST
  • Implement GraphQL Endpont and resolvers
  • Test the API

Alright, no more talking – let's jump right in!

add graphql dependencies
# install all dependencies for graphql to work in our API
pnpm api add graphql @apollo/server @graphql-tools/load @graphql-tools/graphql-file-loader

The graphql package is now installed. we can proceed to configure our API. firstly we need to create a graphql folder in the api folder. this is where our graphql files will be stored. i will create a main.graphql file in that folder.

We are going to have three types initialized in this file:

  • Response - custom type for responses
  • Query - required root graphql query type
  • Mutation - required root mution type
api/graphql/main.graphq file
type Response {
message: String!
success: Boolean!
error: String
}

type Query {
hello: String
}

type Mutation {
hello: String
}

Up next we need to move some code from main.ts to be isolated to their own module. i am refering to the router which is set inside the main file.

api/src/main.ts file
// ... rest of code
const router = express.Router();
// Set initial route
router.get("/", (_req, res) => {
res.send({ message: "Monorepo API Configured!", success: true});
});
// .. rest of code

We are moving this part to a separate module called api/src/endpoints/rest/restEndpoints.ts. where we will do some modifications and export the module

api/src/endpoints/rest/restEndpoints.ts
// api/src/endpoints/rest/restEndpoints.ts
import express, { Router } from "express";

const restEndpoint: Router = express.Router();
// define routes
restEndpoint.get("/", (_req, res) => {
res.send({ message: "Rest Endpoint Initialized!" });
})

export { restEndpoint }

Now lets updated api/src/main.ts to use the rest endpoint.

api/src/main.ts file
import { restEndpoint } from './endpoints';

// ... rest of code
// Set v1/api endpoint
app.use("/v1/api", restEndpoint);
// .. rest of code
info

As observed, the import originates from the ./endpoint module rather than ./endpoint/rest/restEndpoint. This is accomplished by incorporating a barrel file within the endpoints folder, which exports all submodule functionalities. read more on what a barrel file is here.

We can run our server again to be sure that we did not break anything.

run server
# run server
pnpm api dev
# our server should be running properly as expected.
# http://localhost:9100 or http://localhost:9000 depending on the PORT set in .env file

Hold on, this isn't GraphQL yet; we're still operating a REST API! yes you are right, its one of the steps toward adding multiple routes tou our API. Up next we are going to create another route for our API inside api/src/endpoints/graphql/graphqlEndpoints.ts module. Additionally we also need to create a resolver module api/src/endpoints/graphql/resolvers.ts which is going to be responsible for implementating all resolvers.

api/src/endpoints/graphql/graphqlEndpoints.ts
// api/src/endpoints/graphql/graphqlEndpoints.ts
import { ApolloServer } from '@apollo/server';
import { loadSchemaSync } from '@graphql-tools/load';
import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader';
import { resolvers } from '.';
// load schema from graphql files
const typeDefs=loadSchemaSync('./graphql/*.graphql', {
loaders: [
new GraphQLFileLoader()
]
});

// create apollo server
const server = new ApolloServer({
typeDefs,
resolvers
});

// export server
export { server}

please note this is a minimal configuration for now.


api/src/endpoints/graphql/resolvers.ts
// api/src/endpoints/graphql/resolvers.ts

export const resolvers = {
Query: {
hello: () => `Hello there welcome to api monorepo`
},
Mutation: {
hello: (_: unknown, args: { name: string }) => `Hello ${args.name} welcome to api monorepo`

}
}

A sigh of relief we are now ready to test our REST + GraphQL APIs running on the same server.

run server
# run server
pnpm api dev
# our server should be running properly as expected.
# http://localhost:9100/v1/rest and http://localhost:9000/v1/graphql
info

We can now happily commit our changes. and push them to our remote repository under the feature/part3 branch.