Answer the following questions:
- A good start to understand the repo is to check the
package.json. What is the HTTP server library used here? What development tools are configured? - What does the following scripts do?
dev,test - What is the entry point of the server?
- What do you think why is the
app.tsand theserver.tsare separated? - If you would create a new endpoint in which file would you put it?
- In the tests which method simulates the HTTP request?
- What do you think what does the
ts-nodepackage do?
Fastify is an HTTP server just like Express JS, Nest JS or Hapi JS.
The goal of Fastify as its name suggests to be fast and modular.
It is pretty similar to Express, but uses a little bit different methods and properties on its Request and Reply (Response in Express) objects.
- Implement a new endpoint:
GET /api/good-bye. - It should respond with the following JSON string
{"message": "Good Bye Visitor!"}and200 OKstatus. - To run tests for this task:
npm test -- task2
If we use Fastify with Typescript, we have to define the types of the given endpoint with the type parameter of the route shorthand methods (get, post, put etc...) to get proper types for e.g. the requests body when accessing it with the request.body.
In the route type you can define, the type of the request's body, query, route parameters, headers, and the reply's body.
You have to define this type and pass it as type parameter to the given method:
type SomeRouteType = {
Body?: RequestBodyType;
Querystring?: RequestQuerystringType;
Params?: RequestParamsType;
Headers?: RequestHeadersType;
Reply?: ReplyBodyType
}Example:
type PostPetsRoute = {
Body: {name: string, kind: "cat" | "dog" }
}
app.post<PostPetsRoute>('/api/pets', (request, reply) => {
const pet = request.pet; // pet is correctly typed right now
})How can you figure something like this out?
You can follow these declarations by clicking on them in VSCode, but in the background material also linked the type definition files from GitHub.
As the current version (5.0.0) of Fastify it is defined on
RouteShorthandOptions interface's RouteGeneric type param.
The RouteGeneric interface extends two interfaces: RequestGenericInterface and ReplyGenericInterface. These two interfaces provide the given type shape.
Deep dive:
- https://github.com/fastify/fastify/blob/v5.0.0/types/route.d.ts
- https://github.com/fastify/fastify/blob/v5.0.0/types/request.d.ts
- https://github.com/fastify/fastify/blob/v5.0.0/types/reply.d.ts
- Create one endpoint which can handle both requests:
POST /api/beverages/coffee,POST /api/beverages/tea,POST /api/beverages/chai. - Use the path parameters to accomplish this task.
- The response's body should be JSON formatted:
{drink: <drink type>}, based on the 3rd part of the path and a200 OKstatus code. - Example: if
POST /api/beverages/coffeeinvoked, the result is{drink: 'coffee'} - Do not forget to create a proper path type for the route parameters.
- To test this task issue:
npm test -- task4
- Extend the
POST /api/beverages/<drink>(<drink>can be coffee, tea or chai) endpoint with a query string.milk=<yes or no>&sugar=<yes or no>. - The query params are optional. If some of them missing it is considered as "no".
- Extend the reply's body with a
with: []prop. This array should contains the'milk'string if the milk query param wasyesand similarly the'sugar'if the sugar param wasyes. - Example:
POST /api/beverages/tea?sugar=yesshould respond with the following body in JSON format:{drink: 'tea', with: ['sugar'] }. - To test this task issue:
npm test -- task5
- Extend the
POST /api/beverages/<drink>endpoint with a request body. - The body contains the kind of a coffee or a tea. It is defined with an object, only one property:
kindwhich is a custom string.{kind: <kind of the main ingredient>}. - The kind should be added as a prefix for the drink.
- Example:
{kind: 'English Breakfast'}request body in JSON format is sent to thePOST /api/beverages/teaendpoint, the response should be{drink: 'English Breakfast tea'}. - To test this task issue:
npm test -- task6
-
Extend the
POST /api/beverages/<drink>endpoint with a customCodeCool-Beverages-Dietaryheader. -
If the header's value is
lactose-intoleranceand amilkwas given in the query string, it should be replaced withlf-milkin the response. -
If the header's value is
veganand amilkwas given in the query string replace the milk withoat-milk. -
To test this task issue:
npm test -- task7 -
Hint: Header names are case insensitive, Fastify converts it to lowercase:
codecool-beverages-dietary -
Hint: When given a header to the Rest Client no quotations marks are needed neither the header name nor the value.
CodeCool-Beverages-Dietary: vegan
- The
POST /api/beverages/<drink>should be respond from now with a201 Createdstatus code instead of the200 OK. - If the drink is not a tea or chai respond with a
418 I'm a teapot🫖 status code. - To test this task issue:
npm test -- task8
Fun:
The server should always validate the user input, because it can be mistakenly wrong or intentionally evil (e.g. hacking your site).
- Respond with a
400 Bad Requeststatus code and a{reason: 'bad drink'}body, if the drink path param is something else than'tea','chai'or'coffee'. - To test this task issue:
npm test -- task9
Fastify can provide a handy option to automatically check different HTTP building blocks.
The shape of the given request part (e.g.) can be described with a JSON Schema.
E.g. we can validate our beverages's path parameters with the following object:
const paramsSchema = {
type: "object",
properties: {
drink: { enum: ["tea", "coffee", "chai" ]}
},
required: ["drink"],
additionalProperties: false
}- It tells that the given data should be an object.
type: "object" - It should have a
drinkproperty.properties: { drink: {...} }. - The drink property is an "enum", it has just the given values only:
drink: { enum: ["tea", "coffee", "chai" ]}. If we want to allow any string, it should be drink:{ type: 'string'}. - The object's
drinkproperty is obligatory (because by default all properties of an object in a JSON schema is optional).required: ["drink"]. - And finally the object does not have any other properties listed in the properties:
additionalProperties: false
It can be added to a route by providing the options param for the route handler:
const paramsSchema = {
type: "object",
properties: {
drink: { enum: ["tea", "coffee", "chai" ]}
},
required: ["drink"],
additionalProperties: false
}
app.post<PostBeveragesRoute>(
'/api/beverages',
{
schema: {
params: paramsSchema
}
},
(request, reply) => {
const { drink } = request.params
}
)- This code will automatically respond
400 Bad Requeststatus and built-in error object in the response body if something else is parsed as the route params. - It is in general a good practice to validate all user inputs at the entry point of the endpoint and later trust the given values and do not check everywhere.
- Fastify Reference: Validation and Serialization
- JSON Schema Docs: Objects
- JSON Schema Docs: Enum
- JSON Schema
- JSON Schema Validator
- Use the Fastify's route validation to validate the body of the
POST /api/beverages/<drink>endpoint.
- Use the Fastify's route validation to validate the body of the
POST /api/beverages/<drink>endpoint.
Deserialization is a process when the HTTP requests body is converted to the programming language's internal representation. (For example: JSON --> JS Object).
Fastify automatically deserialize the request body if the content type is either application/json or text/plain.
Serialization is a reverse process. The internal representation is converted to something that is transferred through the HTTP response body. (For example: JS Object --> JSON).
In Fastify it is possible to define a scheme for the response body. It has some benefits.
- Speeds up the HTTP server, because we tell exactly what we are looking for in the response, and do not need check everything in it with a recursive algorithm.
- It can filters out all non explicitly stated data from the response so it is harder to leak out something we don't want.
Your task:
- Define the shape of the HTTP response's body using JSON Schema syntax in a case of
201 Createdstatus. - The body should fulfill all implemented task's requirements.
Isn't tiring to write basically the same thing twice? The JSON Schema and the route's type. Basically we are defining the same thing in two different language (Typescript and JSON Schema).
Would be nice if the somehow Fastify can figure out the types from the JSON Schema.
It is actually possible with the type providers.
- Configure the JSON Schema to TS type provider for the server.
- Skip the type parameters from the route declaration.
Note: if something is wrong with the JSON Schema, you won't get any error messages, but the type of the given request part will be unknown. In this case You can check the JSON schema's validity with e.g. an online JSON Schema validator.