loader
banner

Building Basic API

Welcome. Making API calls and render the responses on a web page is fun. But, creating an API and querying it is wonderful! Besides, creating an API allows us to gain more insight into how APIs work, what is the logic behind them, and what is going on in the back-end when an API call is received. So if you feel that you have done enough making API calls so far in your projects, and you decided to deep down to understand the whole process of any application, I can say that this article may help you.

We are going to build a todo application in this project. We will use these technologies:

  • Express.js
  • GraphQL
  • Apollo
  • Mongo
  • React

Our project structure is going to be like shown on the below graphic:

This series of articles is planned to consist of 3 episodes. These episodes will have these headers:

  • Build basic API
  • Build user authentication
  • Render and manipulate data on front-end

In this episode, we are going to build a basic API. It will contain one query to get Todo items and three mutations to create items, modify and delete them.

Here is the final code of this epiose

Ready? Let’s go then!

Requirements

  • NodeJS 14v+
  • NPM or YARN
  • NPX
  • React Developer Tools
  • Apollo Client Devtools

Building Development Environment

Create a folder named todo-app and create two more folders in the todo-app folder, named server and client.

Open the todo-app/server folder in a code editor and run this command to build a NodeJS project and install dependencies:

npm init -y && npm i express apollo-server-express graphql mongoose dotenv cors && npm i --save-dev nodemon @babel/core @babel/node @babel/preset-env babel-loader

Now create your back-end project structure like shown in the below:

After creating all files and directories, open up the .babelrc file and write these lines into it:

{
"presets": ["@babel/env"]
}

The configuration we made in the .babelrc file is going to provide us to use ES6 while module importing.

Before going further on server configuration, let’s create a MongoDB cloud account.

MongoDB Configuration

To configure MongoDB, let’s create an account in mongodb.com/cloud . Later on create an organization, a project in the organization, and a cluster in the project. After the cluster is created, in the page where the cluster is in, click Database Access. Click ADD NEW DATABASE USER choice there and create a database user. Later on, click the Cluster option on the left bar. On the opened page, click the Connect button. In the Setup connection security step, choose Allow anywhere option and click Choose a connection method. Then let’s click the Connect your application option and copy the connection information under Add your connection string into your application code. Let’s go back to the text editor, and create a variable named MONGODB_URL in the .env file and assign the MongoDB connection information to the variable. Change with the password just created in the Database Access step and change the myFirstDatabase as todolist:

MONGODB_URL="mongodb+srv://username:12345@cluster0.mjh9d.mongodb.net/todolist?retryWrites=true&w=majority"

Now, open up the db/connection/index.js file, and write these lines down:

import dotenv from "dotenv";
import mongoose from "mongoose";
dotenv.config();
const dbConnection = () =>

  new Promise((resolve, reject) => {
    mongoose.connect(process.env.MONGODB_URL, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
      useFindAndModify: false,
    });

  const db = mongoose.connection;

  db.on("error", () => {
    console.error.bind(console, "connection error:");
      reject(
        new Error(
          "Connection error has occurred when trying to connect to the database!"
      )
    );
  });
  db.once("open", () => resolve("🚀 Successful database connection."));
});
export default dbConnection;

By this configuration, we define the connection information of our MongoDB and we provide a promise function that can be used anywhere in the project. It is a good idea to create Promise functions, and add them error handling lines, as much as we can when coding a backend project with JavaScript.

Later on, open up the db/models/TodoModel.js file and fill it with these lines:

import mongoose from "mongoose";

const todoSchema = new mongoose.Schema({
  user: String,
  title: String,
  mission: String,
  isDone: Boolean,
},
{ timestamps: true });

const Todo = mongoose.model("Todo", todoSchema);

export default Todo;

Now we have created the schema of our collection which will place on the database and reproduced a model from the schema. Let’s continue with server configuration, and run the server to connect to the database.

Server Configuration

Below we have created the MongoDB configuration. Now, let’s use it! Open up the server.js file and write these lines to it:

import express from "express";
import dbConnection from "./db/connection";

const startServer = async () => {

  await dbConnection()
    .then((result) => console.log(result))
    .catch((err) => console.log(err));

  const app = express();

  app.use("/", (req, res) => res.send("Welcome to Todo App"));
app.listen(4000, () => console.log(`🚀 Server listening on port 4000`));
};

startServer();

Now open the package.json file and write this line into the script section:

"start": "nodemon ./server --exec babel-node -e js",

After writing the start script, your package.json‘s scripts section should look like this:

"scripts": {
  "start": "nodemon ./server --exec babel-node -e js",
  "test": "echo \"Error: no test specified\" && exit 1"
},

Open up a console and run the server with this command:

npm start

f you see these two messages on the output of the terminal, it means you made configurations successfully:

🚀 Successful database connection.
🚀 Server listening on port 4000

It is time to build GraphQL schema and run Apollo server!

Building GrapQL Schema & Apollo Server

To build a GraphQL structure, it is a good idea to start with creating type definitions. To do it, open the schema/typeDefs/index.js file and write these lines:

import { gql } from "apollo-server-express";

const typeDefs = gql`
  type Todo {
    user: String
    title: String
    mission: String
    createdAt: String
    updatedAt: String
    isDone: Boolean
 }

  input TodoInput {
    user: String
    title: String
    mission: String
    isDone: Boolean
  }

  type Query {
    getTodoList: [Todo]
  }
  type Mutation {
    addTodo(todo: TodoInput): Todo
  }`;

export default typeDefs;

This way we have defined a Todo type that is parallel with the database collection schema, an input type that is also parallel with both the Todo type and the Todo schema. We have also defined built-in GraphQL types: Query and Mutation. We will define a resolver object and we will use the same property name for the Query and Mutation types in it; additionally, we are going to assign MongoDB query functions to the properties in the resolver object. We are going to write the query functions into models. Later we will merge the type definitions, resolvers, and models in the Apollo Server configuration, and in this way, we will build the relationship between the type definitions, the resolvers, and the models.

Now, let’s create the models. To do it open up the schema/models/index.js file and write these lines down into it:

import Todo from "../../db/models/TodoModel";

const generateTodoModel = () => ({

queries: {
  getAll: () =>
    new Promise(
      async (resolve, reject) =>
        await Todo.find({}, (err, todo) =>
          err ? reject(err) : resolve(todo)
      )
    ),
  },
  mutations: {
    addTodo: (todo) =>
      new Promise((resolve, reject) =>
        new Todo(todo).save((err, todo) => (err ? reject(err) : resolve(todo)))
      ),
  },
});

export default generateTodoModel;

In this way, we have created the query functions which will be executed on the Todo model we imported, and assign those functions to the model placed in the GraphQL schema we created. Later, in the resolver object, we will call these functions via context object which is defined in the Apollo server, and assign them to properties as values. For now, let’s create the resolver object and assign console.log() functions to the properties. To do that, open the schema/resolvers/index.js and write these lines into it:

const resolvers = {
  Query: {
    getTodoList: async (parent, args, context) =>
      console.log(JSON.parse(JSON.stringify(args.todo)), context)
  },
  Mutation: {
    addTodo: async (parent, args, context) =>
      console.log(JSON.parse(JSON.stringify(args.todo)), context)
  },
};

export default resolvers;

Now, we have created the type definitions, the resolvers, and the models. It is time to create the Apollo Server configuration and build the relation with those GraphQL elements. To achieve this, open up the server.js file and change it like that:

import express from "express";
import { ApolloServer } from "apollo-server-express";
import typeDefs from "./schema/typeDefs";
import resolvers from "./schema/resolvers";
import generateTodoModel from "./schema/models";
import dbConnection from "./db/connection";
import cors from "cors"

const startApolloServer = async () => {

  await dbConnection()
    .then((result) => console.log(result))
    .catch((err) => console.log(err));

  const app = express();
  app.use(cors())

  const server = new ApolloServer({
    typeDefs,
    resolvers,
    subscriptions: { path: "/subscriptions" },
    context: ({ req }) => {
      return {
        models: {
          Todo: generateTodoModel(),
        },
      };
    },
  });
  await server.start();

  server.applyMiddleware({ app });

  app.use((req, res) => {
    res.status(200);
    res.send("Welcome Todo App");
    res.end();
  });

  await new Promise((resolve) => app.listen({ port: 4000 }, resolve));

  console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`);
  console.log(
`🚀 Subscriptions ready at ws://localhost:4000${server.subscriptionsPath}`
  );
  return { server, app };
};

startApolloServer();
`

As you can see, we build the Apollo Server and create the relation between the type definitions, the resolvers, and the models in it. Now, check out the terminal where you ran the npm start command before. If you see these messages on the output of the terminal…

🚀 Successful database connection.
🚀 Server ready at http://localhost:4000/graphql
🚀 Subscriptions ready at ws://localhost:4000/subscriptions

…congratulations, you have an Apollo Server running on successfully!

Do you see the context function in the Apollo Server configuration? It takes the req object from the express server as a parameter. It doesn’t use it now, however. Anyway, the more important thing is to see what the context function returns - it returns the models we defined in the GraphQL Schema!

Now, in the resolvers, let’s call the functions which are defined in models. Remember, we just logged to the console the query elements so far. Instead of just logging things to the console, let’s run a real database query via the resolvers. To do it, open up the schema/resolvers/index.js and change it like that:

const resolvers = {
  Query: {
    getTodoList: async (parent, args, context) =>
      await context.models.Todo.queries.getAll(),
  },
  Mutation: {
    addTodo: async (parent, args, context) =>
      await context.models.Todo.mutations.addTodo(
        JSON.parse(JSON.stringify(args.todo))
      ),
  },
};

export default resolvers;

With this configuration, we have been able to run the MongoDB queries using GraphQL query language. To run a real query, open localhost:4000/graphql address in a browser and run a mutation which will create a new todo, like that:

If you see the data object, it means you have done the right configuration and you have added a todo object to the MongoDB. To be sure, check the MongoDB collection. Click the Collections tab in the Cluster page in the MongoDB Cloud. On the page that came up, click the collection named todo under the database named todolist.

So far, we have added one Query resolver and one Mutation resolver. Let’s add two more Mutation resolvers to modify and delete the todo item. Todo that, open the schema/models/index.js file, and write these to function into the mutations object:

modifyItem: (body) =>
  new Promise(
    async (resolve, reject) =>
      await Todo.findByIdAndUpdate(body.id, body.query, (err, todo) =>
        err ? reject(err) : resolve(todo)
        )
  ),
  deleteItem: (id) =>
    new Promise(
      async (resolve, reject) =>
        await Todo.findByIdAndDelete(id, (err, todo) =>
          err ? reject(err) : resolve(todo)
        )
   ),

Then go to the schema/resolvers/index.js file and write these line into the Mutations:

modifyItem: async (parent, args, context) =>
  await context.models.Todo.mutations.modifyItem(
    JSON.parse(JSON.stringify(args))
  ),
deleteItem: async (parent, args, context) =>
  await context.models.Todo.mutations.deleteItem(
    JSON.parse(JSON.stringify(args.id))
  ),

Finally, go to the schema/typeDefs/index.js file and write these lines into the Mutations type:

modifyItem(id: ID!, query: TodoInput): Todo
deleteItem(id: ID!): Todo

Now, open localhost:4000/graphql in the browser. And run the GetTodos query:

Copy the _id property from returned value, open a new tab on the Apollo playground and run a ModifyItem mutation like that:

See the updatedAt and isDone property has changed. In MongoDB connection configurations, we provided the updateAt property to change in every update action, and we have changed the isDone with a query ran on the Apollo server. Let’s run a delete query. Open a new tab in the playground and run a delete query like that:

And that’s it! We have an API on which we can run GraphQL queries, we have a NoSQL database and we have an Apollo Server which integrated with express.js. Well done! Next time we are going to reproduce JSON Web Token based on API calls and make user authentication and authorization configurations on Apollo Server!