Quickstart: Customer Registry in TypeScript
Learn how to create a customer registry in TypeScript, package it into a container, and run it on Kalix.
Before you begin
-
If you’re new to Kalix, create an account
so you can try it out for free.
-
You’ll also need to install the Kalix CLI to deploy from a terminal window.
-
For this quickstart, you’ll also need
If you want to bypass writing code and jump straight to the deployment:
|
Writing the Customer Registry
-
From the command line, create a directory with the basic structure for your project using a template:
npx @kalix-io/create-kalix-entity@latest customer-registry --typescript --template basic
-
Change into the project directory:
cd customer-registry
-
Download and install project dependencies:
npm install
Define the external API
The Customer Registry service will create or retrieve a customer, including their name, email, and mailing address. The customer_api.proto
will contain the external API your clients will invoke.
-
Create a
proto
directory.mkdir proto
-
Create a
customer_api.proto
file and save it in theproto
directory. -
Add declarations for:
-
The protobuf syntax version,
proto3
. -
The package name,
customer.api
. -
Import
google/protobuf/empty.proto
and Kalixkalix/annotations.proto
.
proto/customer_api.protosyntax = "proto3"; package customer.api; import "google/protobuf/empty.proto"; import "kalix/annotations.proto";
-
-
Add the service endpoint. The service endpoint is annotated with
kalix.codegen
indicating we want to generate a Value Entity for this service.proto/customer_api.protoservice CustomerService { option (kalix.codegen) = { value_entity: { name: "customer.domain.Customer" entity_type: "customers" state: "customer.domain.CustomerState" } }; rpc Create(Customer) returns (google.protobuf.Empty) {} rpc GetCustomer(GetCustomerRequest) returns (Customer) {} }
-
Add messages to define the fields that comprise a
Customer
object (and its compoundAddress
):proto/customer_api.protomessage Customer { string customer_id = 1 [(kalix.field).entity_key = true]; string email = 2; string name = 3; Address address = 4; } message Address { string street = 1; string city = 2; }
-
Add the message that will identify which customer to retrieve for the
GetCustomer
message:proto/customer_api.protomessage GetCustomerRequest { string customer_id = 1 [(kalix.field).entity_key = true]; }
Define the domain model
The customer_domain.proto
contains all the internal data objects (Entities). The Value Entity in this quickstart is a Key/Value store that stores only the latest updates.
-
Create a
customer_domain.proto
file and save it in theproto
directory. -
Add declarations for the proto syntax and domain package.
proto/customer_domain.protosyntax = "proto3"; package customer.domain;
-
Add the
CustomerState
message with fields for the customer data, and theAddress
message:proto/customer_domain.protomessage CustomerState { string customer_id = 1; string email = 2; string name = 3; Address address = 4; } message Address { string street = 1; string city = 2; }
-
Run the
build
script from the project root directory to generate source classes, based on the protobuf definitions, in which you can add the business logic:npm run build
Create command handlers
Command handlers, as the name suggests, handle incoming requests before persisting them.
-
If it’s not open already, open the generated
src/customer.ts
file for editing. -
Modify the
Create
handler by adding the logic to handle the command. The complete function should include the following:src/customer.tsCreate(customer, _customerState, ctx) { // API and domain messages have the same fields so conversion is easy const customerState = CustomerState.create(customer); ctx.updateState(customerState); return Reply.message({}); },
-
The incoming message contains the request data from your client and the command handler updates the state of the customer.
-
-
Modify the
GetCustomer
handler as follows to handle theGetCustomerRequest
command:src/customer.tsGetCustomer(getCustomerRequest, customerState) { if (!customerState.customerId) { const id = getCustomerRequest.customerId; return Reply.failure(`Customer ${id} has not been created.`); } else { // API and domain messages have the same fields so conversion is easy return Reply.message(customerState); } },
-
If that customer doesn’t exist, processing the command fails.
-
If the customer exists, the reply message contains the customer’s information.
-
The conversion between the domain CustomerState and the external API is straightforward, as they have the same fields.
-
The |
Package and deploy your service
To build and publish the container image and then deploy the service, follow these steps:
-
If you haven’t done so yet, sign in to your Kalix account. If this is your first time using Kalix, this will let you register an account, create your first project, and set this project as the default.
kalix auth login
-
Update the
config.dockerImage
setting in thepackage.json
file with your container registry. -
Use the
deploy
script to build the container image, publish it to the container registry as configured in thepackage.json
file, and then automatically deploy the service to Kalix usingkalix
:npm run deploy
-
You can verify the status of the deployed service using:
kalix service list
Invoke your service
Once the service has started successfully, you can start a proxy locally to access the service:
kalix service proxy customer-registry --grpcui
The --grpcui
option also starts and opens a gRPC web UI for exploring and invoking the service (available at http://127.0.0.1:8080/ui/).
Or you can use command line gRPC or HTTP clients, such as grpcurl
or curl
, to invoke the service through the proxy at localhost:8080
, using plaintext connections.
A customer can be created using the Create
method on CustomerService
, in the gRPC web UI, or with grpcurl
:
grpcurl \
-d '{
"customer_id": "abc123",
"email": "someone@example.com",
"name": "Someone",
"address": {
"street": "123 Some Street",
"city": "Somewhere"
}
}' \
--plaintext localhost:8080 \
customer.api.CustomerService/Create
The GetCustomer
method can be used to retrieve this customer, in the gRPC web UI, or with grpcurl
:
grpcurl \
-d '{"customer_id": "abc123"}' \
--plaintext localhost:8080 \
customer.api.CustomerService/GetCustomer
You can expose the service to the internet. A generated hostname will be returned from the expose command:
kalix service expose customer-registry
Next steps
-
You can learn more about Value Entities.
-
Do another Quickstart to learn about Event Sourcing and Event Sourced Entities.