Develop Todo Service
#
Introduction for Todo appThe proposed todo-app we are about to develop has a relatively simple interface, roughly as shown below:
It contains the following functions:
- Enter the new todo content and press enter to add it
- For each todo item
- You can toggle the
completed/active
status - You can delete
- You can double-click to start editing
- You can toggle the
- All todo items with
completed
status can be made clear.
We need to implement a set of server-side api
's and that support:
getTodos
to get the currenttodos
addTodo
adds a newtodo
removeTodo
Removetodo
updateTodo
updatetodo
clearCompleted
to clear all completedtodos
Then the frontend reuses the server-side type and interface call code via farrow-api
to develop the interface based on react
.
#
Todo ServiceCreate server/api/todo.ts
file and import the depended modules。
Then the Todo Schema
is defined and the types contained in the Todo Schema
are extracted by TypeOf
. Then, we construct the Todos Schema
using the List
function of farrow-schema
.
Note that the above looks a bit redundant, but each part has its role, for example, description
becomes an annotation after codegen
to the front-end and is a way to document the api.
The types obtained by the front-end are shown below.
#
getTodosNext, we can define the first interface getTodos
.
Although getTodos
does not require parameters, the input schema
still needs to be set to {}
, because farrow-api
does not currently support setting interface contracts without parameters.
The output schema
of getTodos
is { todos }
, then we use the in-memory todos
array to emulate the database and return { todos }
directly within the function body of the getTodos
interface.
A simple interface is implemented.
#
addTodoNext, let's define the addTodo
interface.
The addTodo
interface, which contains a non-empty input schema
and a non-empty output schema
, is a bit more complex than the implementation of the getTodos
interface.
When declaring the Todo Schema via class Todo extends ObjectType
, we used the Todo.name
that comes with the class
to access the class name
.
However, the AddTodoOutput
of const AddTodoOutput = Union(InvalidAddTodoInput, AddTodoSuccess);
is a normal variable name and requires the displayName
property to be configured.
This property is not required and will work without it. However, AddTodoOutput
is inlined at codegen
time everywhere it is used, and it is difficult for the front-end to index it directly by type name.
After configuring displayName
, the code is generated as follows.
Another thing worth emphasizing is that we have used Discriminated Unions
or Tagged Unions
to express the Success/Failure
mutually exclusive relationship instead of using the Http status code
.
We can consume the data as follows:
With Tagged Unions
, the back-end interface can force the front-end to handle different cases
, and TypeScript
will constrain how the front-end consumes the data through type checking.
#
removeTodoNext, we add the removeTodo
interface, which is similar to the addTodo
interface.
Define the input schema
, in this case RemoveTodoInput
Define output schemas
, which are merged into output schema
by Tagged Unions
Use input schema
+ output schema
to define the api schema
Implement this interface
It is worth mentioning that the Schema.create(value)
method, such as RemoveTodoSuccess.create
, does not actually do anything special internally, it just returns value
.
The purpose of the create
method is to make the type derivation more accurate, as we often use Literal String Type
as the type of Tag
fields when using the Tagged Unions
pattern. Using a literal directly, such as RemoveTodoSuccess
, will often be derived as a string
type. With the help of the create
method, we can derive the Literal type
more accurately.
#
updateTodoNext, we implement the updateTodo
interface.
The interesting part here is how to define an optional field?
With ?=
together with Nullable(X)
we can define an optional field.
#
clearCompletedImplement the clearCompleted
interface.
Ease as getTodos
。
#
ApiServiceIn fact, the interfaces getTodos
, addTodo
, removeTodo
, etc. implemented above are essentially functions, but through the Schema
definition, we can derive the TypeScript
type and get some metadata
in runtime
as well.
They are just functions, we need to bind it to an http server
, which is done by the following code.
ApiService
can convert a set of entries
functions into farrow-http
router
objects that can be hooked up to an HttpPipeline
.
At this point, we have completed the server-side interface part of TodoService
, so let's take a look at the front-end part of consuming the interface data.