Module 4 (1)
Module 4 (1)
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.
• 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:
• 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:
• Now to test the setAboutMessage field, we can replace the query with a mutation
• 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.
• 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:
• 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:
• 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
• 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:
• Next, we need a resolver for issueAdd that takes in an IssueInput type and creates a
new issue in the in-memory database.
• 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
• 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:
• 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:
• 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 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:
• 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:
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