0% found this document useful (0 votes)
2 views33 pages

Module 4 (1)

The document provides an overview of Express and GraphQL, detailing Express as a flexible web framework that utilizes middleware for functionality, and explaining routing, request/response handling, and middleware usage. It contrasts REST APIs, which are resource-based and use multiple endpoints, with GraphQL, which is graph-based, allows for more granular data retrieval, and operates through a single endpoint. Additionally, it covers the implementation of a GraphQL API, including schema definition, query and mutation types, and resolver functions for handling requests.

Uploaded by

anamijames03
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views33 pages

Module 4 (1)

The document provides an overview of Express and GraphQL, detailing Express as a flexible web framework that utilizes middleware for functionality, and explaining routing, request/response handling, and middleware usage. It contrasts REST APIs, which are resource-based and use multiple endpoints, with GraphQL, which is graph-based, allows for more granular data retrieval, and operates through a single endpoint. Additionally, it covers the implementation of a GraphQL API, including schema definition, query and mutation types, and resolver functions for handling requests.

Uploaded by

anamijames03
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 33

Express and GraphQL

Express
• Express is a flexible web application framework.
• Express relies on other modules called middleware to provide the functionality that
most applications will need.

Routing
• Router takes a client request, matches it against any routes that are present, and
executes the handler function that is associated with that route. The handler function
is expected to generate the appropriate response.
• A route specification consists of an HTTP method (GET, POST, etc.), a path
specification that matches the request URI, and the route handler. The handler is
passed in a request object and a response object.
• The request object can be inspected to get the various details of the request, and the
response object’s methods can be used to send the response to the client.
Example: A route can match a request with a specific HTTP method. So, instead of
app.use(), app.get() has to be used in order to match the GET HTTP method.
• The handler function, the second argument that the routing function takes, can set the
response to be sent back to the caller like:

Request Matching
• When a request is received, then first step Express does is match the request to one of
the routes. The request method is matched against the route’s method.
Example: If route’s method is get() then any HTTP request using the GET method
will match it.

• The request URL is matched with the path specification, the first argument in the
route, which is /hello.
• When a HTTP request matches this specification, the handler function is called.
➢ If you want to match all HTTP methods, we could write app.all().
➢ If we needed to match multiple paths, we could pass in an array of paths, or
even a regular expression like '/*.do' will match any request ending with the
extension .do.

Route Parameters
• Route parameters are named segments in the path specification that match a part of
the URL. If a match occurs, the value in that part of the URL is supplied as a variable
in the request object.
• This is used in the following form:
app.get('/customers/:customerId', ...)
Example:
The URL /customers/1234 matches the route specification. So req.params.customerId
will have the value 1234 for URLs.

Route Lookup
• Multiple routes can be set up to match different URLs and patterns. Router tries to
match all routes in the order in which they are installed. Always the first match is
used.
• If two routes are possible matches to a request, then it will use the first defined one.
So, the routes have to be defined in the order of priority.
Example:
If you want to match everything that goes under /api/, that is, a pattern like /api/*

Handler Function
• Once a route is matched, then handler function will be called. The parameters passed
to the handler are a request object and a response object.
• The handler function is not expected to return any value. But it can inspect the request
object and send out a response as part of the response object based on the request
parameters.
Request Object
• Any aspect of the request can be inspected using the request object’s properties and
methods. A few important and useful properties and methods are listed here:
1. req.params:
➢ This is an object containing properties mapped to the named route parameters.
➢ The property’s key will be the name of the route parameter and the value will be
the actual string sent as part of the HTTP request.
2. req.query:
➢ This holds a parsed query string.
➢ It’s an object with keys as the query string parameters and values as the query
string values.
➢ Multiple keys with the same name are converted to arrays, and keys with a square
bracket notation result in nested objects
➢ Example: order[status]=closed can be accessed as req.query.order.status
3. req.header, req.get(header):
➢ The get method gives access to any header in the request.
➢ The header property is an object with all headers stored as key-value pairs.
4. req.path:
➢ This contains the path part of the URL, that is, everything up to any ? that starts
the query string.
➢ Usually, the path is part of the route specification, but if the path is a pattern that
can match different URLs
5. req.url, req.originalURL:
➢ These properties contain the complete URL, including the query string.
➢ Note that if you have any middleware that modifies the request URL, originalURL
will hold the URL as it was received, before the modification.
6. req.body:
➢ This contains the body of the request, valid for POST, PUT, and PATCH requests.
➢ Note that the body is not available (req.body will be undefined) unless a
middleware is installed to read and optionally interpret or parse the body.

Response Object
• The response object is used to construct and send a response.
1. res.send(body):
➢ This method can accept a buffer (in which case the content type is set as
application/octet-stream as opposed to text/html in case of a string).
➢ If the body is an object or an array, it is automatically converted to a JSON string
with an appropriate content type.
2. res.status(code):
➢ This sets the response status code. If not set, it is defaulted to 200 OK.
➢ One common way of sending an error is by combining the status() and send()
methods in a single call like res.status(403).send("Access Denied").
3. res.json(object):
➢ This is the same as res.send(), except that this method forces conversion of the
parameter passed into a JSON, whereas res.send() may treat some parameters like
null differently.
➢ It also makes the code readable and explicit, stating that you are indeed sending
out a JSON.
4. res.sendFile(path):
➢ This responds with the contents of the file at path.
➢ The content type of the response is guessed using the extension of the file.

Middleware
• Middleware functions have access to the request object (req), the response object
(res), and the next middleware function in the application’s request-response cycle.
• The next middleware function is commonly denoted by a variable named next.
• Middleware called express.static is used to serve static files. This is the only built-in
available as part of Express.
• Middleware can be at the application level or at a specific path level.
• To use a middleware at the application level is to supply the function to the
application
app.use(middlewareFunction);
• Static middleware is constructed by calling express.static() method.
• In order to use the same middleware for only requests matching a certain URL path,
say, /public, the app.use() method would have to be called with two arguments, the
first one being the path,
app.use('/public', express.static('public’))

REST API
• REST (Representational State Transfer) is an architectural pattern for application
programming interfaces (APIs).
Resource Based
• The APIs are resource based. APIs are formed by a combination of resources and
actions. There are only resource names called endpoints.
• Resources are accessed based on a Uniform Resource Identifier (URI), also known as
an endpoint. Resources are nouns (not verbs). We use two URIs per resource:
1. The collection (like /customers)
2. Individual object (like /customers/1234), where 1234 uniquely identifies a
customer.
HTTP Methods as Actions
• To access and manipulate the resources, we use HTTP methods.
• While resources were nouns, the HTTP methods are verbs that operate on them. They
map to CRUD (Create, Read, Update, Delete) operations on the resource.

• HTTP method and operation mapping are well mapped and specified, REST by itself
lays down no rules for the following:
1. Filtering, sorting, and paginating a list of objects. The query string is commonly
used in an implementation-specific way to specify these.
2. Specifying which fields to return in a READ operation.
3. If there are embedded objects, specifying which of those to expand in a READ
operation.
4. Specifying which fields to modify in a PATCH operation.
5. Representation of objects. You are free to use JSON, XML, or any other
representation for the objects in both READ and WRITE operations.

GraphQL
• GraphQL was developed to address some concerns.
• Example: How an object is displayed in a mobile application and the same is
displayed in a desktop browser can be quite different, and therefore, a more granular
control as well as aggregation of different resources may work better.
Field Specification
• In GraphQL, the properties of an object that need to be returned must be specified.
Specifying no fields of an object in a REST API, return the entire object.
• This lets the client control the amount of data that is transferred over the network,
making it more efficient, especially for lighter front-ends such as mobile applications.

Graph Based
• REST APIs were resource based, whereas GraphQL is graph based.
• This means that relationships between objects are naturally handled in GraphQL
APIs.
Example: In the Issue Tracker application, you could think of Issues and Users having
a relation: An issue is assigned to a user, and a user has one or more issues assigned to
them. When querying for a user’s properties, GraphQL makes it natural to query for
some properties associated with all the issues assigned to them as well.

Single Endpoint
• GraphQL API servers have a single endpoint in contrast to one endpoint per resource
in REST. The name of the resource(s) or field(s) being accessed is supplied as part of
the query itself. This makes it possible to use a single query for all the data that is
required by a client.
• Due to the graph-based nature of the query, all related objects can be retrieved as part
of a query for one object.

Strongly Typed
• GraphQL is a strongly typed query language. All fields and arguments have a type
against which both queries and results can be validated and give descriptive error
messages.
• In addition to types, it is also possible to specify which fields and arguments are
required and which others are optional.
• The advantage of a strongly typed system is that
➢ It prevents errors.
➢ It supports the basic scalar types such as integer and string, objects composed
of these basic data types, and custom scalar types and enumerations.

Introspection
• A GraphQL server can be queried for the types it supports.
• This creates a powerful platform for tools and client software to build atop this
information.
• This includes code-generation utilities in statically typed languages and explorers that
let developers test and learn an API set quickly, without grepping the codebase or
wrangling with cURL.

Libraries
• For JavaScript on the back-end, there is a reference implementation of GraphQL
called GraphQL.js. To tie this to Express and enable HTTP requests to be the
transport mechanism for the API calls, there is a package called express-graphql.
• The package graphql-tools and the related apollo-server are built on top of
GraphQL.js to add these advanced features.

The About API


• We will implement one API as well as another API that lets us change the string that
is returned by this API.
• GraphQL schema has two entry points into the type system, called Query and
Mutation. All other APIs or fields are defined hierarchically under these two types,
which are like the entry points into the API.
➢ Query fields are expected to return existing state.
➢ Mutation fields are expected to change something in the application’s data.
• A schema must have at least the Query type. Difference is that query fields are
executed in parallel, mutation fields are executed in series.
• The GraphQL type system supports the following basic data types:
1. Int: A signed 32-bit integer.
2. Float: A signed double-precision floating-point value.
3. String: A UTF-8-character sequence.
4. Boolean: true or false.
5. ID: This represents a unique identifier, serialized as a string. Using an ID
instead of a string indicates that it is not intended to be human-readable.
• In the About API, we have a field called about under Query, which is a string and a
mandatory one.
• The schema definition of the about field is:

• We will use the variable typeDefs when we initialize the server.


• Define another field that lets us change the message and call this “setAboutMessage”.
• To indicate that this field needs a mandatory string input called message, then we
need to write like below

• Use a string data type as the return value for the setAboutMessage field and add it to
the schema under the Mutation type.
• Name the variable that contains the schema typeDefs and define it as a string in
server.js:

• The next step is to have handlers or functions that can be called when these fields are
accessed. These functions are called resolvers because they resolve a query to a field
with real values.
• At every leaf level, the field needs to be resolved using a function of the same name
as the field. At the topmost level, we’ll have two properties named Query and
Mutation in the resolver.
• It is defined as:

• Define message as a variable at the top of the file.

• All the function needs to return this variable. A simple arrow function that takes no
arguments is defined as:
• All resolver functions are supplied four arguments like:
fieldName(obj, args, context, info)
• The arguments are described here:
1. obj:
➢ The object that contains the result returned from the resolver on the parent field.
➢ This argument enables the nested nature of GraphQL queries.
2. args:
➢ An object with the arguments passed into the field in the query.
➢ For example, if the field was called with setAboutMessage(message: "New
Message"), the args object would be: { "message": "New Message" }.
3. context:
➢ This is an object shared by all resolvers in a particular query and is used to contain
per-request state, including authentication information, data loader instances, and
anything else that should be taken into account when resolving the query.
4. info:
➢ This argument should only be used in advanced cases, but it contains information
about the execution state of the query.
• The return value should be of the type that is specified in the schema.
• The function definition for setAboutMessage looks like:

• We can assign this function as the resolver for setAboutMessage within the Mutation
top-level field like:

• Next is to initialize the GraphQL server.


• The constructor takes in an object with atleast two properties - typeDefs and resolvers
and returns a GraphQL server object. Here’s the code to do that:

• We need to install the Apollo Server as a middleware in Express.


• We need a path (the single endpoint) where we will mount the middleware. It takes in
a configuration object as its argument that configures the server, of which two
important properties are app and path.
• To install the middleware in the Express application, add the following code:

• Final code snippets of working API server.


• The query language has to be used to write the query. The query needs to follow the
same hierarchical structure of the schema.
• To access the about field, a top-level query has to be used, which contains nothing but
the field we need to retrieve, that is about

• Now to test the setAboutMessage field, we can replace the query with a mutation

• The JSON object looks like:


GraphQL Schema File
• It is separating the schema into a file of its own. Moving the contents is simple.
• Create a file called schema.graphql and move the contents of the string typeDefs into
it.

• The “nodemon” tool restarts the server on detecting changes to files by default.
➢ Only looks for changes to files with a .js extension.
➢ To make it watch for changes to other extensions, we need to add an -e option
specifying all the extensions it needs to watch for.

The List API


• The GraphQL way to specify a list of another type is to enclose it within square
brackets.
• We could use [Issue] as the type for the field, which we will call issueList.
➢ In List return value is mandatory, and each element in the list cannot be null.
➢ Separate the top-level Query and Mutation definitions from the custom types
using a comment. The way to add comments in the schema is using the #
character at the beginning of a line.
Example:
• In the server code, we need to add a resolver under Query for the new field, which
points to a function. We can return this array from the resolver.

• Array is implicit (due to the schema specification) that issueList returns an array, and
therefore, subfields of the field are automatically expanded within the array.
• Here is one query that we can run to test the issueList field.
• This query will result in an output like this:

List API Integration


• To use the APIs, we need to make asynchronous API calls, or Ajax calls. The popular
library jQuery is an easy way to use the $.ajax() function.
➢ For modern browsers support Ajax calls natively via the Fetch API
➢ For older browsers such as Internet Explorer, a polyfill for the Fetch API is
available from whatwg-fetch.
• We will replace the implementation of the loadData() method in the IssueList React
component with something that fetches the data from the server.
• Within the loadData() method, we need to construct a GraphQL query.
• Following is the query for fetching all issues and all subfields:

• We’ll send this query string as the value for the query property within a JSON, as part
of the body to the fetch request.
• The method we will use is POST and we will add a header that indicates that the
content type is JSON.
• Once the response arrives, we can get the JSON data converted to a JavaScript object
by using the response.json() method.
• We need to call a setState() to supply the list of issues to the state variable called
issues, like:

• We can also now remove the global variable initialIssues, as we no longer require it
within loadData().
• The complete set of changes below and it completes the changes required for
integrating the List API.
Custom Scalar Types
• Custom scalar types can be used for creating a custom scalar type date.
• To be able to use a custom scalar type, the following has to be done:
➢ Define a type for the scalar using the scalar keyword instead of the type keyword
in the schema.
➢ Add a top-level resolver for all scalar types, which handles both serialization as
well as parsing via class methods.
• The scalar type has to be defined in the schema using the scalar keyword followed by
the name of the custom type.

• We can replace the String type association with the created and due fields with
GraphQLDate.
• The changes for the scalar definition and the new data types for the date fields are
shown in Listing.
• A scalar type resolver needs to be an object of the class GraphQLScalarType, defined
in the package graphql-tools.
• First import this class in server.js:

• The constructor of GraphQLScalarType takes an object with various properties.


• We can create this resolver by calling new() on the type like:

• Two properties of the initializer - name and description are used in introspection

• The class method serialize() will be called to convert a date value to a string.
• This method takes the value as an argument and expects a string to be returned.

• Two other methods, parseValue() and parseLiteral(), are needed to parse strings back
to dates.
• The complete set of changes in server.js
• Here’s a query for testing

• Here are the results for this query

• A reviver function is one that is called for parsing all values, and the JSON parser
gives it a chance to modify what the default parser would do.
• Create a function that looks for a date-like pattern in the input and converts all such
values to a date.
• We will use a regular expression to detect this pattern, and a simple conversion using
new Date().
• Here’s the code for the reviver:

• We have to get the text of the body using response.text() and parse it ourselves using
JSON.parse() by passing in the reviver, like:

• Below is the complete set of changes for using the Date scalar type
The Create API
• We will implement an API for creating a new issue in the server, which will be
appended to the list of issues in the server’s memory.
• We have to define a field in the schema under Mutation called issueAdd
• GraphQL needs a different specification when it comes to input types. Instead of
using the type keyword, we have to use the input keyword.
• Define new input type called IssueInputs in the schema:

• Add a description for IssueInputs as well as for the property status


• Now, we can use the type IssueInputs as the argument type to the new issueAdd field
under Mutation. The return value of this field can be anything.
• The complete set of changes to the schema is shown below

• Next, we need a resolver for issueAdd that takes in an IssueInput type and creates a
new issue in the in-memory database.

• Set the ID and the created date

• Default the status, if not supplied to the value 'New':

• Append the issue to the global variable issuesDB and return the issue object as is:

• This function now can be set as the resolver for the issueAdd field under Mutation:
• There are two methods that need to be implemented in the GraphQLDate resolver:
1. parseLiteral
2. parseValue
parseLiteral
• The method parseLiteral is called, where the field is specified in-place in the query.
• The parser calls this method with an argument ast, which contains a kind property as
well as a value property.
• The kind property indicates the type of the token that the parser found, which can be a
float, an integer, or a string.
• Here’s the implementation of parseLiteral:

parseValue
• The method parseValue will be called if the input is supplied as a variable.
• The method parseValue will be called if the input is supplied as a variable.
• Consider it as an input in the form of a JavaScript object, a pre-parsed JSON value.
This method’s argument will be the value directly, without a kind specification

• The complete set of changes to server.js is shown below


server.js : Changes for the Create API
• To test the addition of a new issue, we can use the following query

• Running this query should give the following result


Create API Integration
• Integration of the Create API is done with a minor change in the defaulting of the new
issue in the UI.
• Example: Remove setting the status to 'New' and set the due date to 10 days from the
current date.

• Before making the API call, we need a query with the values of the fields filled in.
• Use a template string to generate such a query within the createIssue() method in
IssueList. So, let’s form the query string as follows:

• Use this query to execute fetch asynchronously

• We could use the complete issue object that is returned and add it to the array in the
state as we did before, but it’s simpler (although less performant) to just refresh the
list of issues, by calling loadData() after sending the new issue off to the server.

• The complete set of changes to integrate the Create API is shown below
Query Variables
• GraphQL has a first-class way to factor dynamic values out of the query and pass
them as a separate dictionary. These values are called variables.
• This way of passing dynamic values is quite similar to prepared statements in SQL
queries. To use variables, we have to name the operation first. This is done by
specifying a name right after the query or mutation field specification.
Example:
mutation setNewMessage { setAboutMessage(message: "New About Message") }
• Next, the input value has to be replaced with a variable name.
• Variable names start with the $ character.
Example:
mutation setNewMessage($message: String!) { setAboutMessage(message:
$message) }
• To supply the value of the variable, we need to send it across in a JSON object that is
separate from the query string.
• We need to send across the variables as a JSON object with the name of the variable
(without the $) as a property and its value as the property’s value.
• Replace the template string with a regular string in the query, using the operation
name and variable specification format. The new query string will be like:

• While constructing the body for the fetch() request, in addition to the query property,
specify the variables property as well, which will contain one variable: issue.

Input Validations
• A common validation is where the set of allowed values is restricted.
• The status field in the Issue Tracker application is one such field.
• One way to implement this validation is adding a check against an array of allowed
values as part of the issueAdd resolver.
• But the GraphQL schema itself gives us an automatic way of doing this via
enumeration types or enums.
• An enum definition in the schema looks like this:

• Add this enum type for status called StatusType:

• We can replace the type String with StatusType in the Issue type:

• GraphQL schema allows us to supply default values in case the input has not given a
value for an argument.
• For programmatic validations, we have to have them before saving a new issue in
server.js.
• This can be achieved by having a separate function called validateIssue().
• Create an array to hold the error messages of failed validations.

• Let’s add a minimum length for the issue’s title.

• Let’s also add a conditional mandatory validation, one that checks for the owner being
required when the status is set to Assigned.

• Apollo Server recommends using the UserInputError class to generate user errors.
• Let’s use that to construct an error to throw:
• One way to detect input errors is by checking if the constructed date object is a valid
value. It can be done using the check isNaN(date), after constructing the date.
• Let’s implement this check in parseValue as well as parseLiteral:

• The Apollo Server has a configuration option called formatError that can be used to
make changes to the way the error is sent back to the caller.
• Testing these changes using the application requires temporary code changes
• The value should be supplied as a literal. A valid call to issueAdd will look like:

• Results should show the following new issue added


• If you change the status to an invalid enum like Unknown, we will get back an error
like:

• If you use a string "New" instead, it should show a helpful error message like this:

• To test the date validations, we need to test both using literals and query variables.
For the literal test, you can use the following query:

• The following error will be returned:

Displaying Errors
• Create a common utility function that handles all API calls and report errors.
• We can replace the fetch calls within the actual handlers with this common function
and display to the user any errors as part of the API call.
• Lets call this function graphQLFetch.
• Let’s make the function take the query and the variables as two arguments:
• All transport errors will be thrown from within the call to fetch()
• The subsequent retrieval of the body and parse it within a try-catch block.
• Let’s display errors using alert in the catch block:

• Once the fetch is complete, we’ll look for errors as part of result.errors.

• The error code can be found within error.extensions.code. Let’s use this code to deal
with each type of error that we are expecting, differently.
• For BAD_USER_INPUT, we’ll need to join all the validation errors together and
show it to the user:

• If the data is non-null, we can use it to set the state like this:
• The complete set of changes in App.jsx to display errors is shown below

You might also like