Photo by S O C I A L . C U T on Unsplash
Since then, the framework has gained stability and has gained users substantially. We also see more contributions from the community not only to the core framework, but also building extensions. Thank you for all your contributions to LoopBack and the 160+ extensions published on npmjs.com!
To continue moving towards an open governace model, it's exciting to see LoopBack joined the OpenJS foundation as the incubating project in 2021 and graduated to at-large project the following year.
Lastly, don't forget that HacktoberFest is on this month. No matter you're a new developer to LoopBack or seasoned contributors, your contributions are always welcomed!
]]>As part of the planning for LoopBack moving to OpenJS Foundation, there have been discussions around finding a new home for the blog, parting from the strongloop.com web site. A few alternatives had brought up, such as having the blog posts as part of the loopback.io repo, using a third party blog site and creating a blog site in a separate repo.
Putting the blog posts in the loopback.io repo is a natural progression, since all the documentation and text-based content will be in the same repo. The concern is that is Jekyll-based. Although it worked fine and integrated well with GitHub Pages, many of the maintainers were not well-versed in the world of Ruby. This was compounded by shell scripts that pulled data from other Git Repositories and modified the site as part of the build process. Hence, a lot of time was spent trying to understand how the website was put together and figuring out the best way to tweak it to integrate the new blog.
The "Strong Blog" was also written with Jekyll and hosted on GitHub Pages. Hence lifting and shifting was a real possibility that we contemplated. However, the slow build times and the maintainers' unfamiliarity with Ruby and Jekyll meant that we realised that the website's current stack wasn't worth keeping around in the long run. At the same time, performing a full migration of the website, documentation, and blog in one go was too daunting of a task for the maintainers who were already working on the project on their own time.
The idea of using a third-party service such as Medium was also considered. However utlimately, the lack of control over the interface, the inability to place it in the same domain name as the main website and documentation, and the frustration that stem from a reader being forced to use a third-party service meant that it was quickly off the table.
As a result, we decided to come to a compromise.
After some deliberation we decided to create a hybrid stack which left the website and documentation with Jekyll, and use Docosaurus 2 as the new blogging platform. We found Docusaurus to be a good fit as it is a Facebook-maintained open source project which was written on top of React. Besides its benefit of speedy build times, this JavaScript-based stack provides more opportunities for extensibility and a reduced barrier for contributing and tweaking. The first-class support for MDX was also a plus as it meant we were able to copy over most of our posts with only a few tweaks, and the ability to generate the blog as build artifacts made it compatible with the Jamstack-eque system of GitHub Pages.
Thanks to the amazing work done by Diana, all of the LoopBack 4 blog posts from 2019 and 2020 were migrated to Docusaurus. With her work, we were able to quickly build and see the new blog coming together with all of the past blog posts populating the blog.
When we started building the blog, we hosted it on GitHub Pages in its own Git Repository and served it in its own sub-domain. This allowed us to quickly build and patch any bugs that may have cropped up. However the end-goal was to host it under the same domain as the main website and documentation: https://loopback.io/blog. Hence, we had to create a solution which merged the new blog with the exsitng website codebase.
This solution? A shell script!
Since we already had a precedence for using shell scripts to pull in data from
other Git Repositories, I've decided to write one to do the same thing, but this
time, specifically for the blog. This script clones the
loopbackio/loopback-blog
Git Repository,
generates the blog's build artifacts, and copies it over to the blog
directory
of the Jekyll website. There's additional logic in there to make it more
fool-proof and less verbose but in essence, that was what it did - Clone, build,
and copy. You can also view the
source code
yourself if you'd like to see how it was done. Over time, we hope to refine this
further and make it more reliable, such as using Git Submodules instead of a
clean clone on every build.
As it stands, we now have a blog that all of the maintainers can now contribute to. With this change, we now have revived a new way for us, the maintianers, to engage with our community and we hope that the blog posts can complement the existing documentation and community channels such as Slack, GitHub Discussions, and the mailing list.
This is just the first step in modernising our website stack. There's still plenty of work to do, and it's our vision to fully migrate our stack to one powered by the JavaScript ecosystem.
With the launch of our new blog, we're excited to announce an upcoming blog series where the maintainers can share their expriences and journey in adopting LoopBack 4. Through this series, we hope to share with the community the unique perspectives of those who work closely on LoopBack 4, not only as users but as maintainers as well.
Don't miss out on future blog posts by subscribing to our RSS or Atom feeds which contain the full articles.
]]>Hope you had a wonderful Thanksgiving for those who celebrate it! In November, LoopBack team focused on improving the context module and documentation, as well as bug fixes. The Toronto squad participated in the CASCONxEVOKE conference. Instead of a physical booth, we held a virtual one online. We welcomed @nflaig as the new maintainer of loopback-next
.
Read more to know about the highlighted improvements:
A new phase init()
was added to the application life cycle events. It is used when a component need to contribute bindings asynchronously. For example:
export class MyComponent implements Component, LifeCycleObserver {
// ...
async init() {
// Contribute bindings via `init`
// This cannot be done via constructor since it's synchronous.
const val = await readFromConfig();
this.app.bind('abc').to(val);
this.status = 'initialized';
this.initialized = true;
}
}
You can check the Component page to learn about its usage.
toInjectable()
was introduced as a shortcut to decorate a common/provider/dynamic-value-factory class and automatically creating binding for them. For example:
@injectable({scope: BindingScope.SINGLETON})
class MyController {
constructor(@inject('my-options') private options: MyOptions) {
// ...
}
}
binding.toInjectable(MyController);
The decorator's usage is well documented on page Binding.
PR #6701 updated test cases to reflect how the design types of array/undefined/complex properties are retrieved.
Method injection is allowed for the lifecycle methods in PR #6740. For example:
class MyObserverWithMethodInjection implements LifeCycleObserver {
status = 'not-initialized';
init(@inject('prefix') prefix: string) {
this.status = `${prefix}:initialized`;
}
start(@inject('prefix') prefix: string) {
this.status = `${prefix}:started`;
}
stop(@inject('prefix') prefix: string) {
this.status = `${prefix}:stopped`;
}
}
In some cases, your Express middleware wants to access LoopBack's RequestContext to resolve certain bindings. This can be done via MIDDLEWARE_CONTEXT
property of the Express request object, which is set up by LoopBack when the RequestContext
is instantiated. For example:
import {MIDDLEWARE_CONTEXT, RequestContext} from '@loopback/rest';
function expressHandler(req, res, next) {
const reqCtx = (req as any)[MIDDLEWARE_CONTEXT];
// Now you have access to the LoopBack RequestContext
}
The guide for calling REST APIs and SOAP services were separated to make the steps involved clearer. You can check the overview page Accessing services and its sub-topics Calling SOAP web services and Calling REST APIs for details.
For troubleshooting, we added steps for creating breakpoints in vscode in the documentation. You can find more details in PR #6743.
BadRequestError
for invalid inclusion relation name by rejecting the request with statusCode as 400.As mentioned in our recent blog post, your contribution is important to make LoopBack a sustainable open source project.
Here is what you can do:
Let's make LoopBack a better framework together!
]]>In October, we were excited to see an increasing number of community contributions as people joined the Hacktoberfest event. This month we had pretty balanced improvements in each area of the framework, including context, health check, OpenAPI specification and documentations. Keep reading to learn about the recently added features!
We welcomed @mrmodise as the maintainer of loopback4-shopping-example
. And we'd like to thank everyone @nflaig, @MattiaPrimavera, @mdbetancourt, @mrmodise, @frbuceta, @HrithikMittal, @simlt, @hectorleiva, @pktippa, @VergilSkye, @kerolloz, @arondn2, @mayank-SFIN571 for your contributions in October!
Here are the highlighted improvements:
APPLICATION
, SERVER
and REQUEST
has been introduced to allow better
scoping of binding resolutions. The limitation of the previous scopes is explained in section choose the right scope, and section resolve a binding value by key and scope within a context hierarchy explains how different scopes determine the binding resolutions.Allowed array query parameter for a single value, like {tags: 'hello'}
where parameter tags
is a string array. See PR #6542.
Supported property level configuration for hidden fields, like @property({type: 'string', hidden: true}) password: string
. This is the shortcut for specifying the hidden properties in model settings. See PR #6484.
save()
method throwing error due to missing idName
is fixed in PR #6640.
modifySpec()
turns to an async function to allow async spec updates. See PR #6655.
A force clean rebuild was added to the pre-start script for the LoopBack 4 examples. You can run npm start
after removing artifacts without manually cleaning the /dist
files. See PR #6588.
Turned on exit
for mocha tests for the created LoopBack applications. See PR #6475.
Module @loopback/socketio was added to use socket.io to expose controllers as WebSocket friendly endpoints.
Enable/disable the metrics endpoints in explorer when mounting the metric and health extensions. See PR #6646 and PR #6645.
Only add MetricsObserver
, MetricsPushObserver
and expose /metrics
endpoints when they are enabled. See PR #6644.
The health check for applications running in container now returns a more accurate HTTP status code based on the state. For example, checking /health
for application in states 'STARTING', 'STOPPING' or 'STOPPED' returns 503. You can find more details in PR #6648.
LoopBack 4 targets both API developers and extension developers, while the current website doesn't distinguish them clearly. This month we restructured the sidebar to classify the documentation into two parts: "Building LoopBack Applications" and "Extending LoopBack Framework". You can check https://loopback.io/doc/en/lb4/Customizing-server-configuration.html to view the new layout.
The instructions for implementing HTTP redirects and mounting an Express router are extracted into a standalone page under "How-to guides". You can check https://loopback.io/doc/en/lb4/Customizing-routes.html to view the content.
Moved server recipes to how-to guides Customizing-server-configuration. See PR #6663.
Two examples were added last month:
Example webpack was added to demo LoopBack running inside the browser as client-side JavaScript application.
Example socketio gives a basic implementation of socketio with LoopBack 4.
You can also download the examples by using the lb4 example
command.
As mentioned in our recent blog post, your contribution is important to make LoopBack a sustainable open source project.
Here is what you can do:
Let's make LoopBack a better framework together!
]]>It's been 6 months since we created the Slack channel for LoopBack community. Thanks to your support, over 500 members had joined and new members are joining almost everyday! Let's take a look at the October edition of the “Community Q&A Monthly Digest”, capturing some of the Q&A in this forum.
Question: How to get the raw request in LoopBack 4 in a function without changing the parsing for the entire app?
Answer:
It's possible to get the raw request body with x-parser
: https://loopback.io/doc/en/lb4/Parsing-requests.html#extend-request-body-parsing.
-- Answered by @Rifa Achrinza
Question: Is there any solution for tracking database migration, for example, migrations has been already run and possible rollback of migration?
Answer: I created a module which tracks migrations and executes scripts based on the db version compared to the app version, see https://www.npmjs.com/package/loopback4-migration. --Answered by @nflaig
Ideally, LoopBack generates the DDL for users to review, and then it’s up to the users to run it or not. It's a feature to be implemented, see https://github.com/strongloop/loopback-next/issues/4757. --Answered by @Diana Lau
Question: I want to check whether a specified categoryId
exists in a Mongo datasource, how can I do that? For example,
{"categories" : [
{
"categoryId" : "e759c15e-3552-4557-aa6b-c1396674c7e6",
"name" : "test"
},
{
"categoryId" : "e759c15e-3552-4557-aa6b-c1396674c7e5",
"name" : "test1"
}
]}
I tried await this.usersRepository.find({'categories.categoryId': 'e759c15e-3552-4557-aa6b-c1396674c7e5'});
but getting an error message below:
> { 'categories.categoryId': string; }' is not assignable to parameter of type 'Filter<Users>'. Object literal may only specify known properties, and ''categories.categoryId'' does not exist in type 'Filter<Users>'
Answer:
The object you pass into .find()
needs to be a Filter
object. Make sure you import { Filter } from '@loopback/repository';
, then you can:
const existingCategoryFilter: Filter = {
//...filter properties in here...
};
let existingCategories = await this.categoryRepository.find(existingCategoryFilter);
-- Answered by @Jackson Hyde
To add on what @Jackson Hyde has mentioned, due to limitations on TypeScript types, nested objects are not included in the typings. Hence, you'll also need to override TypeScript's check by adding // @ts-ignore
just before the repository function.
-- Answered by @Rifa Achrinza
Question: I want to implement JWT refresh token in LoopBack 4. Can you suggest any good tutorial?
Answer: You can follow this https://loopback.io/doc/en/lb4/JWT-authentication-extension.html. --Answered by @Pratik Jaiswal
Question: I used LoopBack CLI to create a "SHIPPING" model but it tries to do lowercase "Shipping" in the SQL with the quotes. An error occurred in the SQL statement because it is case sensitive with the quotes around it. How can I fix this? I'm on LoopBack 4 and using the loopback-connector-ibmi
.
Answer: Did u try to give the name in your model?
@model({name: 'member_membership'})
export class MemberMembership extends Entity {
//...
}
So member_membership
is the table in the database.
-- Answered by @Mohammed
The name
property customizes the model name, which is default to the class name if not provided. The model name is then used as the default for table name unless you further customize it for specific databases.
-- Answered by @Raymond Feng
Question: I have a CORS issue with passport-login
example when trying to establish connection with Google using Angular Frontend. I keep getting CORS error:
Access to XMLHttpRequest at 'https://accounts.google.com/o/oauth2/v2/auth?...' (redirected from 'http://localhost:3000/api/auth/thirdparty/google') from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource."
But the application is working fine with jade.
Answer: In your login component you could do something like:
OAUTH2_LINK_GOOGLE = this.api_url+'/api/auth/thirdparty/google?redirect_uri=' + this.redir_url
onGoogleSignIn() {
window.location.href = this.OAUTH2_LINK_GOOGLE;
}
Bind the Google link to the above in the HTML code.
-- Answered by @marg330
As mentioned in our recent blog post, your contribution is important to make LoopBack a sustainable open source project.
Here is what you can do:
Let's make LoopBack a better framework together!
]]>Fall is the season of the harvest. We're glad to see the good progress that we've made in the past 3 months, together with the contributions from the community. In September, there is record high percentage (25.6%) of the merged PRs which are coming from the community. Thank you all!
If you haven't heard of Hacktoberfest happening this month, check out the details in our previous blog on Hacktoberfest. There's still time to join. For those who participated, we appreciated your contributions.
Let's take a look some of the highlights in the last quarter by you and the LoopBack team.
One of our strategies to add value to LoopBack is to leverage third-party libraries and integrate with them. We created the following extensions:
@loopback/typeorm
: enables TypeORM support in LoopBack@loopback/graphql
: integrates with TypeGraphQL for creating GraphQL API https://loopback.io/doc/en/lb4/GraphQL.html@loopback/rest-msgpack
: adds support to allow receive MessagePack requests and transparently convert it to a regular JavaScript object. It provides a BodyParser implementation and a component to register it. To better organize our content and easier for navigation/discovery, we reorganized our content based on four quadrants: tutorials, how-to guides, concepts and reference guides. To find out more how our documentation is organized, see this documentation page.
Besides the ongoing refactoring work, we enriched the content and clarified on some of the topics that are frequently asked by you. For example, we:
There were many features, fixes and enhancements in the past few months, and here are some highlights:
@loopback/authentication-jwt
extensionIn a recent blog post, we shared our views on encouraging more community contributions to match with our growing user community. The switch to Developer Certificate of Origin (DCO) as the contribution method is a change we made to make your contribution process easier. We also created a community extension documentation page to showcase LoopBack extensions built by the community.
In addition, we are pleased to have @nabdelgadir and @madaky to be one of our community maintainers. We appreciate the great work you’ve done and welcome to the team.
There are many more accomplishments that cannot be captured in this blog, make sure you check out our previously published monthly milestone blog posts in Q3 for more details:
Your contribution is important to make LoopBack a sustainable open source project.
Here is what you can do:
Let's make LoopBack a better framework together!
]]>LoopBack 3 is approaching its end-of-life at the end of the year for community support. For LoopBack 3 users, we hope the migration guide helps you migrate your applications to version 4.
At the same time, we understand you might still be using LoopBack 3 and/or in the process of migrating to LoopBack 4. For the next few months after the EOL date, we'll try to support the community in the following ways:
Please note that the December 2020 end-of-life date is applicable to community support. If you are using LoopBack as part of the IBM API Connect v5 or v2018 product, check with the product announcement for its end-of-support schedule.
If you already have LoopBack 3 applications running in production, it is highly recommended for you to review the Understanding the differences between LoopBack 3 and LoopBack 4 page as mentioned in one of our older blog posts. There is also the migration guide helping you to migrate your LoopBack 3 applications incrementally.
Your LoopBack 3 applications will continue to work even after LoopBack 3 reaches end of life. There will be very minimal, if any, changes going into the codebase. In the case of addressing security vulnerabilities, you might need to fork the corresponding GitHub repos and apply security fixes. See this blog for the list of Node.js packages reaching end-of-life along with loopback
repo.
Your contribution is important to make LoopBack a sustainable open source project. Here is what you can do:
Let's make LoopBack a better framework together!
]]>We bring another month of new features, fixes, and improvements in documentation and developer experience in LoopBack. Make sure to update your compatible projects with lb4 update
if you want to update the underlying libraries to their latest versions.
Also, as part of our long-term effort to encourage more community contributions, we are participating in this year’s Hacktoberfest. You can read more about the event and participation details at our Hacktoberfest blogpost.
There has been a continuous effort to improve our documentation. Below are some highlights for this month:
We spent a good amount of time to improve the experience of using ObjectID
with LoopBack. We have identified the direction we want to take and the tasks to work on. You can learn more about the spike in issue 3456.
.onStart()
and .onStop()
methods of the Application
, so that they can be used to register observers as plain functions for the start and stop life-cycle events.lb4 update
command to be runnable against any projects that use @loopback/*
dependencies in dependencies
or devDependencies
, or peerDependencies
; not just LoopBack 4 projects.migrate
and openapi-spec
scripts.@injectable
, so that @injectable
can be used instead of @bind
, which is in tune with other frameworks using Dependency Injection. @bind
is not removed from the framework, so apps using @bind
will not be affected.keepAliveTimeout
, headersTimeout
, maxConnections
, maxHeadersCount
, and timeout
properties of the underlying HTTP server instance configurable by specifying them in the application config object.@inject
.extension-
prefix from the affected extensions for their names to be consistent with other extension modules.Shout out to Rifa Achrinza for explaining the differences between weak and strong relations in PR 6404, MessagePack PR, and his numerous other PRs.
Opening issues are community contributions too, so thanks to all those who help LoopBack become better by reporting bugs and usability issue. We try to address popular issues with higher priority, so continue to let us know the problems you face on GitHub or Slack.
As mentioned in our recent blog post, your contribution is important to make LoopBack a sustainable open source project.
Here is what you can do:
Let's make LoopBack a better framework together!
]]>As part of our long-term effort to encourange more community contributions, LoopBack is going to participate in this year's Hacktoberfest.
Hacktoberfest is an annual event encouraging participation in the open source community, open to everyone. Whether you’re new to development, a student, or a long-time contributor, you can help drive the growth of open source and make positive impact on an ever-growing community. All backgrounds and skills levels are encouraged to complete the challenge.
You can learn more about Hacktoberfest values at https://hacktoberfest.digitalocean.com/details#values.
Participants completing the challenge (contributing at least 4 valid pull requests) will earn a limited edition T-shirt.
Please note that any prizes are subject to conditions set by Hacktoberfest. IBM and the LoopBack team are not involved and not responsible in any way.
Joining the effort is simple. Just pick any problem that is itching you and send a pull request to fix it. You can also choose one of LoopBack's issues labelled as Hacktoberfest or good first issue if you need inspiration.
Make sure to familiarize yourself with Hacktoberfest's Participation rules and Quality standards; and also LoopBack's Contributing guide.
Are you new to open source? No problem, we have you covered! Check out Hacktoberfest's Beginners guides and LoopBack's guide on submitting a pull request to LoopBack 4.
For the entire month of October, LoopBack's maintainers team will be giving extra priority to pull request reviews and helping new contributors along their journey to the first accepted contribution.
We are primarily looking for quick wins with long term impact: small enhancements improving your day-to-day experience while using LoopBack.
As you may know, we have introduced a new documentation system in June to better organize the content and make it easier to find the information you need (see New Documentation Structure and Alignment Along Abstraction Levels). The reorganization is still in progress and we would love to get some help. In particular, extracting recipes from "Concepts" explanations into proper "How-to guides" is an easy way how to quickly score valuable pull requests. See the GitHub issue loopback-next#5783 for the list of docs pages to rework.
Having said that, all improvements are welcome!
We are looking forward to receive and eventually land your pull requests. Let's make LoopBack a better framework together!
]]>Welcome back to the September edition of the "Community Q&A Monthly Digest", in which we are curating some of the Q&A that we think it might be helpful to you. Thank you for posting your questions and helping your fellow LoopBack users.
The LoopBack Slack community is a platform where LoopBack users are helping each other out. If you haven't joined already, sign up today!
Let's take a look at some of the questions and answers from the community.
Question: Is it possible to use auto-generate timestamp property in a model?
Answer: To set the property to the current datetime upon Model Create, you can:
@property({
type: 'date',
defaultFn: 'now',
})
timestamp?: string;
Question: Does LoopBack has any built-in cache? Or should we implement that to make response even faster?
Answer: We currently don’t but there are some example implementations for your reference:
Question: I want to perform schema migration but the order of tables is important beacause there are some relation and foreign key between them. How can I set the order of tables to be migrated?
Answer: Within migrate.ts
, app.migrateSchema
accepts a model
array. So it can be updated as such:
await app.migrateSchema(Object.assign(<SchemaMigrationOptions>{
models: [/* Add model names here */]
}, existingSchema));
Like with any auto-migration, please do take a backup of the database before running the migration.
Question: I wanted to implement an API like the file-transfer
example on the docs but with more endpoints with different storage directories. How is that possible?
Answer: You can have different endpoints backed by different methods using post /<url>
decoration as you see at https://github.com/strongloop/loopback-next/blob/master/examples/file-transfer/src/controllers/file-upload.controller.ts#L28. If you want to calculate the file path per request, you can instantiate a new upload service instance instead of using the injected one.
Questions: I work with LoopBack in a k8 cluster, when i try to implement JWT authentication, all users get the same token, and the data in that token is not equal to that user. Is there any way to fix it besides saving the token in a database?
Answer: Typically JWT tokens are generated using a combination of a secret and some sort of UUID of the user. When they successfully authenticate, a token is generated and returned. When you need to verify the token, you decode it using the secret, giving you the UUID of the user. This means that you don’t actually have to save a token at all. Here’s how we are generating tokens for our users.
const userInfoForToken = {
id: userProfile.id,
name: userProfile.name,
roles: userProfile.roles,
};
// Generate a JSON Web Token
let token: string;
try {
token = await signAsync(userInfoForToken, this.jwtSecret, {
expiresIn: Number(this.jwtExpiresIn),
});
} catch (error) {
throw new HttpErrors.Unauthorized(`Error encoding token : ${error}`);
}
In this context, signAsync
is a promisify
’d version of jsonwebtoken.sign()
.
Question: I want to use the LoopBack app cli-command programmatically. Is this possible?
Answer:
You can try to require('@loopback/cli/lib/cli')
. See https://github.com/strongloop/loopback-next/blob/master/packages/cli/lib/cli.js. cli.js
has logic to create yeoman env and register generators.
As mentioned in our recent blog post, your contribution is important to make LoopBack a sustainable open source project.
Here is what you can do:
Let's make LoopBack a better framework together!
]]>Our focus in August is the documentation restructure. The layout of several main sections are reorganized for easier navigation. Another significant improvement is about the request handling. More flexible approaches of adding LoopBack style middleware and express middleware are introduced in @loopback/rest
.
We would like to appreciate Agnes Lin's great contributions during her internship. There has been so much fun and pleasure working with her! She will be continue helping us part time. Good luck with your school.
Keep reading to learn about the improved documentation and recently added features!
The fundamental concepts were listed in section "Behind the Scene" sorted by the publish date. To have a concise name and a more organized layout for users to search, we renamed the section to be "Concepts" and restructured the documentations into the following sub-sections:
You can visit the more organized contents in https://loopback.io/doc/en/lb4/Concepts.html.
The "How-to Guide" is also reorganized by topics. The existing tutorials are classified into the following sections for users to search quickly:
You can visit the more organized contents in https://loopback.io/doc/en/lb4/.
We added documentation for LoopBack 4 types including the syntax of model property definition in page https://loopback.io/doc/en/lb4/LoopBack-types.html.
We added project layout for LoopBack 4 applications in page https://loopback.io/doc/en/lb4/Loopback-application-layout.html, users can find each file's meaning and responsibility in the application scaffolded by lb4 app
.
We are seeing more users creating extensions and it's a good time to make the extension creation experience easier and smoother. Therefore the extension generator and related documentations are updated to align with the latest code base. You can check the latest material in:
And run lb4 extension
to create extensions with the new component template.
The term "legacy juggler bridge" might give the wrong impression to users that the loopback-datasource-juggler
won't be supported or not working well because of the "legacy" word. So we removed the misleading word "legacy" across the documentations and CLI prompts.
loopback-datasource-juggler
is still well maintained and we have a plan to modernize it. Feel free to join the discussion in issue #5956 if you are interested.
The current connector contents are mixed with how-to guides, references and tutorials. The spike story 5961 came up with a better plan to reorganize them into the four quadrants layout:
A big feature took place in @loopback/rest
to support more flexible ways to add express middleware for handling requests. PR #5366 added a middleware based sequence and wrapped existing actions as middleware. The new usage is documented in the following pages:
PR #6062 optimized middleware based sequence and its middleware providers to be singletons:
There are several improvements made for easier debugging and error tracing in @loopback/rest
module:
PR #6159 added more debug information for request parsing, routing, and method invocation. The debugging keywords are loopback:rest:find-route
, loopback:rest:invoke-method
, and loopback:rest:parse-param
.
The route description is improved in PR #6188 to include the verb and the path.
PR #6171 added HTTP server options and status information in the debug string. The debugging keyword is loopback:http-server
.
To make your contribution process simpler, we have changed the contribution method from CLA to DCO for loopback-next
and most of the connector repositories. Be sure to sign off your commits using the -s
flag or adding Signed-off-By: Name<Email>
in the commit message. For more details, see https://loopback.io/doc/en/contrib/code-contrib.html.
PR #6172 makes sure the REST options are passed to http-server.
PR #6105 Reworked the validation code to use exiting RestHttpErrors.invalidData
error. This way the error object includes the parameter name in the error message & properties and has a machine-readable code property.
Thank you for the contribution coming from the community. Here are some of the contributions that we'd like highlight:
Thanks to Nico Flaig's contribution! Now @loopback/authenticate
supports applying multiple authentication strategies to one endpoint. The new syntax of decorator is:
@authenticate(
strategyOne | strategyOneWithOptions,
strategyTwo | strategyTwoWithOptions
)
myFunction() {}
The new syntax is well documented in page Authentication Decorator.
We appreciate Madaky's feature PR #5589 which adds the token refresh service in extension @loopback/authentication-jwt
. You can check the new guide to try it.
Many thanks to Rifa Achrinza's contribution in PR 6153. The order filter now supports string value as the shortcut in addition to the existing array value. You can specify an order filter as {order: 'name DESC'}
.
As mentioned in our recent blog post, your contribution is important to make LoopBack a sustainable open source project.
Here is what you can do:
Let's make LoopBack a better framework together!
]]>Almost two years ago, LoopBack 4 was released and announced at Node+JS Interactive event. Thanks to your support, we now have over 110k downloads per month on npmjs.com and over 3000 GitHub stars on the loopback-next repo. Recently, we created the LoopBack Slack community to provide a platform for the community and us helping each other. We are glad to see an increasing engagement in that front as well!
With the core of the framework maturing and contributions shifting to the LoopBack extensions, we think LoopBack has entered a new stage and it's time to revisit our approach to planning work, delivering features, fixing bugs, and improving documentation.
As we started the rewrite of LoopBack from ground up back in early 2017 (see the kick-off post), it quickly became clear the scope is huge and we must be very disciplined in planning and scoping features if we want to reach a meaningful release in a reasonable time. We were working in short iterations, burning through a backlog of tasks we identified as required for the initial release. Eventually we published the first Developer Preview in November 2017, followed by more preview releases, until we finally released LoopBack 4 GA in October 2018. Keeping a tight control over backlog prioritization and milestone planning allowed us to achieve this great milestone.
Such a tight planning worked great while it was mostly the IBM core team working on the project. On the flip side, this process made it sometimes difficult to juggle our time between working on our roadmap vs helping community contributors; and often created the impression that the core team will eventually implement all missing features, given enough time, and community contributions are not really necessary. This was acceptable while the framework offered only a limited subset of features needed to build real applications, because there were only few early adopters to support and it was indeed the core team that was contributing most of the improvements.
As more and more users discover and try LoopBack, they may find feature gaps, identify bugs, or even come up great ideas. If the perception is that somebody else (the core team) will eventually implement those features, then there are little incentives for users to step up and join the project as contributors. As a result, the number of maintainers is not keeping up with the growing number of users, leading to ever-increasing load on existing maintainers and eventually maintainers burning out or leaving the project. (You can read more on this topic in Healthy Open Source and Sustaining and growing LoopBack as a successful open source project).
Many of us has experienced this ourselves in the past, when LoopBack 3 got to the stage where the maintainers were overwhelmed with the amount of incoming issues and pull requests. We feel it's time to turn the ship around and make sure we don't repeat the same mistakes in LoopBack 4. As LoopBack 4 user base grows, it is essential to grow our contributor community joining forces to enhance the framework together.
We have been always actively looking for new ways to attract more contributions from our community and grow contributors into maintainers.
To make it easy for our users to find easy-to-implement improvements to contribute, we are adding good first issue
and help wanted
labels on GitHub issues, and listing items we'd like to see progress in our roadmaps and milestones.
In addition, we recently made an announcement about switching the contribution method to Developer Certificate of Origin (DCO) from Contributor License Agreement (CLA). We hope this will make the contribution process easier for you.
Going forward, we would like to focus more on enabling more of you to contribute by mentoring and coaching them to complete their PRs and providing technical guidance on their work if needed. We would also like to create more examples and starter code for experimental features, and invite the community to enhance those features collectively.
To further encourage community contributions, we are going to open our planning process too and start building the roadmap together with our community, based on what tasks individual contributors would like to work on.
One of the strengths of LoopBack 4 is its extensibility. You can create extensions to extend the programming model and integration capability of the LoopBack 4 framework.
We created a number of extensions, for example, the recently published TypeORM and pooling service extensions. These can be served as references to inspire you to build extensions to fit your needs.
Moreover, we'll be working on cleaning up the extension template and documentation, so that the developer experience of building an extension is smoother.
At the same time, we're happy to see more extensions built by the community, see the community extensions page. Let's build this list together by submitting a PR to include your extensions!
We have been investigating a few areas that can further improve LoopBack 4 based on our visions, even more importantly community feedbacks. There are some ideas for inspiration.
Modernizing the juggler has been under our radar. We would like to position LoopBack as the composition layer that brings API/microservice stories together. It will include built-in capabilities such as REST API and Data/Service access as well as integration with other frameworks. We're planning to publish a blog to cover that. Stay tuned.
Below are areas that we've done some initial investigation/implementation and would like to invite you to join us to build a more complete story. It includes continuing to improve our documentation and building more education materials. Likewise, we'll be publishing blog posts to share our plans and visions in the following areas:
Your contribution is important to make LoopBack a sustainable open source project. We hope our plans to adopt DCO, improve the extension development experience and focus on enabling contributors would make your contribution experience smoother and better. We are also planning on sharing our views on a few technologies.
Here is what you can do:
Let's make LoopBack a better framework together!
]]>Welcome to the July edition of the "Community Q&A Monthly Digest", curating some of the Q&A that we think it might be helpful to you. Let's take a look.
Question: Is there a built-in support in LB4 for database retries if the db responds with 429 for example? Or is it possible to overwrite a single method to implement this for all db operations similiar to how entityToData can be overwriten if data should be added to all create/update operations.
Answer: It's better to intercept execution errors inside the datasource, not at repository level. I created a small example to demonstrate the approach. In a real app, I would extract the code into a mixin that can be applied on any DataSource class. https://gist.github.com/bajtos/2379d7c6df31e477aaa3a3f6ea87886c
Question: Is it possible to connect my API to a webhook, so that when an event is triggered, it notifies my API? What documentation could I read about doing this?
Answer: A webhook is just another HTTP request. Depending on the architecture of the lb4 app, this request can be done in a Service, an Interceptor, or a Controller, using either the built-in Node.js API or third-party HTTP request modules such as Axios. Besides that, no special configuration is needed for web hooks.
Question: How to implement findOrCreate
instead await exists
+ await create
?
Answer: You can extend DefaultCrudRepository
with findOrCreate
’s implementation.
Then your repositories extend the custom one. Similar to how AccountRepository
extends MyDefaultCrudRepository
in the tutorial https://loopback.io/doc/en/lb4/Repositories.html#define-repositories.
Question: I started using Loopback 4. How to redirect home screen of LB4 to angular 8 running on some other port?
Answer: It should be possible to replace the .static
function in the app class with .redirect
: https://loopback.io/doc/en/lb4/apidocs.rest.restapplication.redirect.html.
Question: The compiler throws the error error TS2322: Type 'string' is not assignable to type PredicateComparison<..>
when I tried some repository CRUD methods. Is it a bug or anything wrong with my setup?
Answer: It is a type issue. For repository CRUD methods, they may take in Filter
or Where
type parameters. For those cases, you will need to specify the type of it, for example:
const userCount = await this.userRepository.count({tags: 'admin'} as Where<User>);
const vipUser = await this.userRepository.find({where:{tags: 'vip'}} as Filter<User>);
Question: We have a project which includes multi-tenancy based multi-database pattern (We have Common DB which stores Client Informations (Users, DB configs etc), and each clients has own database). How can I perform switching datasource dynamically with the multi-tenancy based project?
Answer: You can check out the multi-tenancy example and the GH issue that discusses about multi-tenancy and dynamic schema selection. You can create datasources at runtime. Meanwhile, if the number of dbs is limited, you can define them upfront and reuse them, see Creating a datasource at runtime. Then the tenant strategy can inject a repository talking to your common DB to load configs per logged in user.
Simply click this invitation link to join. You can also find more channel details here: https://github.com/strongloop/loopback-next/issues/5048.
]]>We can't believe that it is already August! Let's check out the work we did in July:
A HasManyThrough
relation sets up a many-to-many connection through another model. A real-world example is a doctor has many patients through appointments. The relation can be defined with @hasMany
decorator as:
//...
@hasMany(() => Patient, {through: {model: () => Appointment}})
patients: Patient[];
We finished most of implementation in June, and we added HasManyThrough
to the relation CLI and also related documentation so that users could learn it better. Please make sure you have @loopback/repository
with version 2.10.0 or higher installed.
The hasManyThrough
Relation page is being added under Relations page. We introduced the use cases, definitions, and examples of how you can customize the relation to meet your requirements. Nevertheless, as mentioned in the docs, because it is an experimental feature, there are some missing functionalities such as inclusionResolver
. Feel free to join discussions on GitHub or even contribute :D
Command line interfaces (CLI) is a convenient tool to help you create artifacts quickly. We added hasManyThrough
relation to lb4 relation
command. With a few prompts, you can define a hasManyThrough
relation easily:
$ lb4 relation
? Please select the relation type hasManyThrough
? Please select source model Doctor
? Please select target model Patient
? Please select through model Appointment
? Foreign key name that references the source model to define on the through model
doctorId
? Foreign key name that references the target model to define on the through model
patientId
? Source property name for the relation getter (will be the relation name)
patients
Don't forget to install the latest @loopback/cli
to try it out!
One of our recent targets is to upgrade the documentation system. As you can see on the site, we reorganized most of the items in sidebar. In the overview page, the section How is our documentation organized introduces how you can find documentation in the four quadrants.
Besides improving the structure, here are some documentation enhancements we'd like to share:
The @loopback/authentication-jwt
component was created to make adding JWT authentication to your application earlier. We've applied it to the shopping example. To find out more, see the JWT authentication extension documentation page.
A transaction is a sequence of data operations performed as a single logical unit of work. LoopBack 4 has many relational database connectors support such logic requirements. We added a section Accessing multiple models inside one transaction to show how it can be achieved.
We realized that the current AJV Validation documentation is missing a crucial information piece on how to enable custom validation and error messages. Please check out the section Custom validation rules and error messages and Validation example for details.
As LoopBack 4 is growing larger, we decide to hide some low-level tools from users so that the framework looks neat and friendly. In July, we hid module @loopback/openapi-v3
as it can be loaded from @loopback/rest
.
We removed @loopback/openapi-v3
from dependencies and also our CLI template dependencies. If you check the page Extending OpenAPI Specification or other related pages, you will notice it is now hidden and replaced by @loopback/rest
.
If you have a SQL database as back-end service, you can execute raw queries using the execute
method that we have in Repository
, and it works great. Unfortunately, execute
does not work for NoSQL connectors such as MongoDB as they require more than just a command
string and args
array.
In July, we started working on how we can improve LB4 API and MongoDB connector API to make it easy to execute raw MongoDB commands. We added a DataSource.execute
method to the Juggler, and leveraged it to support different execute
styles. We also added support for non-SQL variants of Repository.execute()
in the loopback/repository
module. More works will be done in August. You can check the progress in story Execute raw NoSQL queries on GitHub if you're interested.
There was a story that a boy woke up in one morning and found himself transformed into a gigantic bug. We don't want that to happen, so we fixed a few bugs in July:
As we added the support for coercing query object with schema last month, it exposed a bug that the nested scope filters don't have the correct constraints. It is being fixed and released in @loopback/rest@5.2.1
. Now you can include nested navigational properties using filter like:
{
include: [
{
relation: "orders",
scope: {
// nested relation
include: [
{
relation: "someOtherRelation",
},
...
}
If you're using MongoDB, you would be used to have dollar signs ($) in your queries. However, the dollar signs are not needed in LB4 general queries, and that's why loopback-mongodb-connector users get confused usually. To improve the user experience, we made some changes in the connector loopback-mongodb-connector, so that the connector users won't get errors even if the queries contain extra dollar signs. The change is released in @loopback-connector-mongodb@5.3.0
We are glad to have @nabdelgadir and @madaky to be one of our community maintainers. We appreciate your great work you've done and welcome to the team.
In order to make your contribution process simpler, we will be gradually changing the contribution method from Contribution License Agreement (CLA) to Developer Certificate of Origin (DCO). Take a look at this blog to find out what the changes are and what it means to you.
If you're interested in what we're working on next, you can check out the August Milestone.
In 2020, we look forward to helping you and seeing you around! LoopBack's success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Here's how you can join us and help the project:
Since the beginning of the LoopBack project, we have been using the Contributor License Agreement (CLA) as the contribution method. Contributors need to sign each CLA for each LoopBack repository they're contributing. To simplify the contribution process and encourage community contributions, we are planning to gradually switch to use Developer Certificate of Origin (DCO) as the contribution method.
As an alternative to CLA, a Developer Ceritifcate of Origin (DCO) is a more lightweight contribution method. According to Wikipedia:
Instead a signed legal contract, a DCO is an affirmation that the source code being submitted originated from the developer, or that the developer has permission to submit the code.
The full text of DCO can be found: https://developercertificate.org/.
Community contributions are vital to the success of LoopBack. Since DCO simply needs your affirmation that you are the one who is submitting the code, we hope this switch would make the contribution process simpler and thus encourages more contributions from you!
In addition, this change can help us to reduce the infrastructure cost, including the team's time and effort, to maintain the CLA server.
Currently, the loopback4-example-shopping is already using DCO.
To sign off the commit, you can:
git commit -s -m "feat: my commit message"
Over the next little while, we'll start the switch for the repositories with the most community contributions, namely loopback-next, loopback.io and loopback-datasource-juggler, then we'll roll it out for the connector repos.
You are more than welcome to contribute on something that you find it relevant and interesting to you. If you're simply looking for items that we want help, you can look for GitHub issues with help wanted
or good first issue
labels.
More questions? Feel free to ask in the #loopback-contributors channel of the LoopBack Slack community.
]]>Welcome back to the "Community Q&A Monthly Digest", in which we highlight some of the questions and answers in our LoopBack Slack community here.
Question: Has anyone implemented Casbin on a brand new project? or is there a good tutorial for lb4? I need to have some rbac / roles system in an app.. or what would you suggest to achieve that?
Answer: We have an access control example uses casbin, see the access-control-migration example and its tutorial. The logic on casbin side is only a prototype, the example mainly shows the integration between casbin and LoopBack authorization system.
Question: I am new to LoopBack and so far I really like what it has to offer. I was wondering if anyone knows of a good online course to learn LoopBack. I have worked through the basic tutorials found in the documentation, but I find it easier to listen and follow along to videos.
Answer: Recently one of our community member posts a YouTube tutorial for LoopBack 4 beginners. There is another series of educational videos on a LoopBack introduction and installation.
From our side, there are a few recent videos on our StrongLoop YouTube channel and we're trying to add more. Hope it helps.
Question: I am trying to disable the openapi.json from showing in my loopback 4 application and its not working. I was able to disable the explorer. Any ideas?
Answer: You can use openApiSpec: {disabled: true}
in index.ts
. i.e.
const config = {
rest: {
//..
openApiSpec: {
disabled: true
//..
},
},
};
Question: Does Loopback 4 support extracting cookies from the header? Currently I had to integrate Express server to achieve this.
Answer: You can use express middleware like http://expressjs.com/en/resources/middleware/cookie-parser.html see how to use middleware in https://loopback.io/doc/en/lb4/Express-middleware.html.
Question: Is there a quick way to generate timestamp? Like at the model level generated:true
?
Answer: I recommend to use defaultFn set to one of the following string values:
See also https://github.com/strongloop/loopback/issues/292 and https://loopback.io/doc/en/lb4/Model.html#property-decorator.
It would be great to capture these options in our TypeScript definitions, see https://github.com/strongloop/loopback-next/blob/ae6427322451c914ae54f44dbb656981e7fbbb81/packages/repository/src/model.ts#L34-L42.
Question: Can I use MongoDB update operators in LoopBack apps? How can I enable it?
Answer: Yes, except comparison and logical operators, the mongo connector also supports MongoDB update operators such as max
, rename
, etc. You will need to set the flag allowExtendedOperators
to true
in the datasource configuration. You can find details and examples at MongoDB connector - update operators.
Simply click this invitation link to join. You can also find more channel details here: https://github.com/strongloop/loopback-next/issues/5048.
]]>Over the recent months, this global pandemic has affected our lives in different ways; we hope you all stay safe during these difficult times. The LoopBack team has adapted to new ways of working, including virtually and in new settings. Even though it could be challenging sometimes, we are glad that we were able to complete most of our Q2 plan. Thanks to all the support from the team and the community!
Here's a brief look at the Q2 summary:
One of our main targets in Q2 was to finish the migration guide, and we did it! We accomplished all the items on the migration plan. The Migration guide can be found easily on the LB4 home page. We have instructions that helps you migrate various artifacts and also have docs to explain similarities and differences between LB3 and LB4. From request/response infrastructure to datasource setup, from model definitions to the authentication and authorization, we hope the guide is useful for you when migrating your LoopBack 3 applications to LoopBack 4.
This quarter, one of our targets was to upgrade the documentation system. As we are adding more features and documentation to LoopBack 4, the abundant amount of sidebar entries was overwhelming and difficult to navigate with the old documentation system. We reorganized most of them into the following four parts:
For example, if you'd like to learn how you can secure your LoopBack 4 application, now you can find it easily under the "Tutorials" sections instead of searching through the whole path "Concept -> Authentication -> Authentication tutorial".
Moreover, we also started working on reorganizing most of our documentation to focus on framework-level APIs and de-emphasizing the lower-level building blocks to reduce the complexity. For example, we removed @loopback/express
from framework-level documentation and replaced references to use @loopback/rest instead
.
This is just the first step of our long journey of improving the documentation system. Please let us know if you have any feedback.
We've been using shared content for some topics in both LB3 and LB4, but this might be confusing if the user is not familiar with LB3. To reduce the gap between these two versions, we also rewrote some documentation from LB3 in LB4 style. For example, now you can check usage examples written in LB4 style for the Filters under the page Working with data. What's more is that we also created tutorials for connecting to MySQL, Oracle, PostgreSQL, and MongoDB databases. By following the steps in these tutorials, you'll find it easy to connect to databases with LB4 applications.
The authentication system has changed a lot since it was being used as an experimental feature. It is now more reliable and flexible. We improved it in the following aspects:
We added two examples for different authentication strategies:
TODO example with JWT demos enabling JWT authentication in the Todo application. This is a good example for beginners to follow the authentication system.
Passport Login example shows how to use Passport Strategies in LoopBack 4. If you are using the loopback-component-passport in LoopBack 3, this example can help you migrate your application to LoopBack 4.
We reorganized the authentication documentation to make it more easy to adopt. Instead of throwing all the details to users, now the authentication docs starts with a simple high-level explanation, then it walks through users with several examples with different difficulties to show what the system is capable of and how they can be achieved.
Besides, as we mentioned above, we also added page Migrating authentication and authorization as part of the migration plan as well.
API Connect is a complete, intuitive and scalable API platform provided by IBM.
In Q2, we completed the integration of LoopBack 4 with API Connect v10 which was released in June. When a LoopBack application is scaffolded through the APIC toolkit, the LoopBack-generated OpenAPIv3 spec comes with API Connect specific metadata added, thanks to the LoopBack APIConnect extension. If you're interested, we've been preparing an article on how you can take the APIs created from LoopBack and import them into API Connect for API management. Stay tuned!
Here are some highlights of our work we would like to share!
You might decide to use an alternative ORM/ODM in your LoopBack 4 application, and LoopBack 4 also has such flexibility as it no longer expects you to provide your data in its own custom Model format for routing purposes, which means you are free to alter your classes to suit these ORMs/ODMs.
TypeORM is an ORM that can run in NodeJS and others platforms and can be used with TypeScript and JavaScript, which fits LoopBack 4 well. We implemented initial support for TypeORM in LoopBack 4 in the @loopback/typeorm
package. Please check the README file for the usage and limitations.
LoopBack 4 leverages Express behind the scenes for its REST server implementation. The new Express Package, has enabled injecting single and multiple express middleware functions as Interceptor
s into Controller
invocations and also as a middleware step in the application Sequence
.
Sometimes it might be the case that we want to break our complex application into multiple smaller LoopBack applications. The component application booter composes these sub applications into the main application. This is helpful for building a scalable micro-services application. See the page Booting an Application.
With help from the community users, the experimental feature HasManyThrough
relation is added to LB4. Currently it only has some basic functionalities. The documentation and related CLI will be updated in the near future.
We upgraded mocha
to the new version 8, and it enable parallel execution of Mocha tests. With the option, we can control the number of worker processes and make the testing process more efficient. Details can be found in Running tests section.
Extensions/Extension points is one of the main features of LB4 to make the application extensible. We added the following two extensions in Q2:
JWT authentication: as the authentication system gets popular and more solid, we extracted the JWT authentication system into a separate extension package as an experimental feature, so that users can quickly mount a component to try out the feature. Check authentication-jwt
for details.
LoopBack APIConnect extension: the LoopBack APIConnect extension is ready for use. It provides ApiConnectComponent
that adds an OASEnhencer
extension to contribute x-ibm-configuration
to the OpenAPI spec generated by LoopBack applications.
We're happy to see more users/developers join our community. We appreciate all the help! We've opened a public Slack channel so that developers can ask questions, discuss issues, and share their knowledge to help each other easily.
We also had a several video-calls with LoopBack maintainers. It's nice to get to know each other, share the plans & visions and discuss topics by talking together. Let's continue building LoopBack a better framework together.
Wanna join us? Yes! You're invited 👉 Join LoopBack Channel on Slack.
There are many more accomplishments that cannot be captured in this blog, make sure you check out our previously published monthly milestone blog posts in Q2 for more details:
We have published a blog LoopBack - 2020 Goals and Focus about our plans this year. Here is a summary of the Q3 2020 roadmap:
In 2020, we look forward to helping you and seeing you around! LoopBack's success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Here's how you can join us and help the project:
Documentation restructuring, TypeORM support, and HasManyThrough were the three main accomplishments in the month of June. Based on the community feedback, documentation improvement remains our number one priority in the coming month. Besides, while welcoming Nathan Chen join as a maintainer of the strong-globalize
repo, we said farewell to Deepak.
Here is what we did in the month June:
When writing documentation for new features, we were often struggling to find the right place to put the content and the right form to frame the information. Recently, we discovered a documentation system based on four different functions. It explains why we were struggling and provides a structure to guide us when writing new content.
Documentation needs to include and be structured around its four different functions: tutorials, how-to guides, technical reference and explanation. Each of them requires a distinct mode of writing. People working with software need these four different kinds of documentation at different times, in different circumstances - so software usually needs them all, and they should all be integrated into your documentation.
In June, we explored how to apply this documentation system to our content and implemented first high-level changes in the way how our content is organized. Check out loopback-next#5549 to find more resources about our new documentation system.
In a series of incremental pull requests, we reworked our documentation structure as follows:
The re-organized documentation is already live at loopback.io, take a look and let us know what do you think!
Now that the new structure is in place, we are going to gradually review and update existing documentation content to align it with the new system. You can find the list of relevant tasks in loopback-next#5113. As always, your help is welcome!
As we were incrementally adding new features to the framework and extracting building blocks into standalone packages, our documentation ended up describing concepts from different abstraction layers in the same place, mixing information for framework users with references to low-level building blocks. This resulted in a steep learning curve for new users, because there were so many concept and packages to learn about!
In June, we reorganized most of our documentation to focus on framework-level APIs and deemphasize lower-level building blocks. As a result, we updated our developer documentation to describe which packages are considered as building blocks, see Organization of content. We also:
@loopback/core
instead of @loopback/context
@loopback/metadata
from framework-level documentation and replaced references to use @loopback/core
instead@loopback/express
from framework-level documentation and replaced references to use @loopback/rest
insteadNow we need to update places referring to @loopback/openapi-v3
, as discussed in loopback-next5692. Want to contribute those changes yourself? Submit a PR today!
We refactored the authentication documentation so that it is easier for beginners to follow. As the new entry page, the authentication overview page describes a typical scenario for securing APIs and it also helps you understand what "authentication" means in LoopBack 4. Next you can follow a simple hands-on tutorial secure your LoopBack 4 application with JWT authentication to start exploring this feature. Then you can gradually learn the authentication system's mechanism and how to implement your own authentication strategies.
As the continuation of improving connector documentation, after updating the PostgreSQL connector, we updated the connector page and added three more tutorials for MySQL, Oracle, and MongoDB connectors in June. By walking you through the steps of creating a LB4 application and connecting to a certain database, we hope new users find the tutorial helpful to adopt LoopBack 4 better. Besides the basic setup steps, we also added some sections to explain those questions that are being asked a lot from the community. Check out these documentations under Database connectors.
A HasManyThrough
relation sets up a many-to-many connection through another model. At the moment, LB4 only supports three basic relations: HasMany
, BelongsTo
, and HasOne
.
Thanks to the initial work by codejamninja
and derdeka
, we have a working prototype of the feature.
While functional, the PR is pretty huge and some of the parts are up for discussion. As a result, we started to extract the core parts of the implementation into smaller PRs so that it's easier for review. In June, we had the basic operations working and tests are added. As the next step, we'll be adding documentation.
Stay tuned with the progress by going to loopback-next #5835.
We have implemented initial support for TypeORM in LoopBack. All it takes to enable TypeORM is to compose your app with the TypeOrmMixin
mixin.
import {BootMixin} from '@loopback/boot';
import {RestApplication} from '@loopback/rest';
import {TypeOrmMixin} from '@loopback/typeorm';
export class MyApplication extends BootMixin(TypeOrmMixin(RestApplication)) {
...
}
For details about using TypeORM with LoopBack, refer to the @loopback/typeorm
doc.
Complete support for TypeORM is a significant amount of work. While the initial work is done, we're looking for ways to improve the implementation in the following areas.
number
,
string
, and boolean
are supported)We upgraded mocha
to the new version 8. This version brings support for running tests in parallel (yay!), but also drops support for --opts
argument and test/mocha.opts
file. See Mocha 8.0.0 release notes for the full list of breaking changes and instructions on migrating existing projects. Our changes were introduced by loopback-next#5750 and loopback-next#5710; and published in @loopback/build
version 6.0.0
.
Miroslav benchmarked the performance of LoopBack and found an opportunity for a quick but significant improvement. By changing the algorithm used in @loopback/context
to generate unique context instance names, we managed to improve the performance of our REST API layer by 45%! Learn more in the blog post How We Improved LoopBack REST Performance by 45%.
A new method exportOpenApiSpec()
was added to the RestServer
for generating OpenAPI specs in JSON or YAML format. This method can be called from the project directory by running the openapi-spec
script.
When a binding key is not bound,ResolutionError
now captures more contextual information. Earlier it used to print a long stack trace and was not easy to find out where the failure happened.
The implementation of binding cache was improved to prevent race conditions and better handle bindings in async conditions.
CoreBindings.APPLICATION_INSTANCE
now has corresponding @config()
decorator.
In the month of July we will continue focusing on improving the documentation. You can see the whole list on the July milestone issue.
There is also ongoing work to have native GraphQL support and a new extension for pooling service. Your feedback is welcome.
We're pleased to welcome Nathan Chen as the maintainer of the strong-globalize repo. Thank you Nathan for all the good work you've done. On the other hand, it's sad to see Deepak leaving the LoopBack team. We wish him best of luck in his new adventure.
In 2020, we look forward to helping you and seeing you around! LoopBack's success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Here's how you can join us and help the project:
Since we made the LoopBack Slack community available, we are happy to see more and more users are joining. Let's see some of the questions and answers that we've highlighted below.
Question: In the Model documentation page, it says "To define a model for use with the juggler bridge, extend your classes from Entity". What's the juggler bridge?
Answer: the Juggler bridge is used to bridge the gap between @loopback/repository
and loopback-datasource-juggler
. The former is used by LoopBack 4 to help define Models, Repositories, etc. It also allows for cross-datasource relations, etc. as they are enforced at the application level instead of the database.
The latter is the ORM/ODM that builds the queries and interacts with the database. It's from LoopBack 3 and is probably the only major component that didn't get revamped to keep backwards-compatibility.
Hence, the Juggler bridge helps bridge the gaps between these Node.js packages.
Entity
is, at it's core, a model that has an ID property. Looking at the source code for Entity
, there's quite a bit of boilerplate code added.
Question: Is there a way to change the application port to string ? I am trying to deploy the application under Azure web app where the port is a string.
Answer: Use port: +(process.env.BILLING_PORT || 3000),
. The +
converts a string to number. For the pipe, you should use path
property instead of port
. See https://github.com/strongloop/loopback-next/blob/master/packages/http-server/src/tests/integration/http-server.integration.ts#L272.
Question: I have a model with a field which is defined as “number”. Working with Postgres. How should I define it to have the field as a double and not an integer ?
Answer: You can specify the dataType field to define a certain type of that column. For type Double, for example,
@model()
export class Item extends Entity {
@property({
type: 'number',
id: true,
generated: false,
})
id?: number;
@property({
type: 'number',
postgresql: {
dataType: 'double precision',
},
})
price?: number;
....
Then run npm run build
and then npm run migrate
commands, the table should have columns:
price | | double precision
Besides the data type, LB4 also allows you to describe tables via the model definition and/or property definition. See Data Mapping Properties for information.
Question: Is there client sdk for lb4 for api code generation? I tried with swagger codegen, but the generated code seems doesn't work.
Answer: You should try lb4 openapi --client
. It generates strongly-typed LoopBack service proxies over openapi spec using TypeScript. We use it to generate SDKs in TS.
Simply click this invitation link to join. You can also find more channel details here: https://github.com/strongloop/loopback-next/issues/5048.
]]>Recently, I measured the performance of LoopBack 4 and found an opportunity for an easy but significant improvement in the way how we are building per-request context instances.
I have been always interested in performance, from high-level design patterns like caching & memoization, to micro-optimizations at language level (anybody remembers CrankShaftScript from Node.js 0.10 days?), all the way down to Mechanical Sympathy.
Since we started rewritting LoopBack from scratch in 2016/17, our primary focus was on features; performance aspects were a bit off the radar. (Not entirely, we were careful to design our HTTP routing layer to avoid the low performance of regexp-matching approach used by Express.) Now that LoopBack 4 is pretty mature, I though it's a good time to take a quick look on how fast is our framework.
To quickly get a high-level overview, I installed Clinic.js and benchmarked our TodoList example application. I used the following TodoList query (filter
) for all benchmarks:
{
"include": [
{
"relation": "todos"
}
]
}
The filter value needs to be URL-encoded, producing the following URL to pass to Clinic.js - let's save it to a shell variable for later use.
$ URL="/todo-lists?filter=%7B%0A%20%20%22include%22%3A%20%5B%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%22relation%22%3A%20%22todos%22%0A%20%20%20%20%7D%0A%20%20%5D%0A%7D"
The Doctor component performs an overall diagnosis looking for common anti-patterns. After installing Clinic.js globally, I ran the following command in examples/todo-list
directory of loopback-next monorepo:
$ clinic doctor --debug --autocannon $URL -- node .
Server is running at http://127.0.0.1:3000
Running 10s test @ http://localhost:3000/todo-lists?filter=(...)
10 connections
(...)
18k requests in 11.05s, 11.1 MB read
Analysing data
Generated HTML file is file:///(...)/todo-list/.clinic/41598.clinic-doctor.html
You can use this command to upload it:
clinic upload .clinic/41598.clinic-doctor
The report says there were no issue detected, hooray!
A closer inspection shows that our process is consuming quite a lot of memory. We don't have any memory leaks, which is most important. However, excessive allocations put more pressure on garbage collector which does impact application's performance. Let's set the memory usage aside for now and check how much time is the application spending on waiting for asynchronous operations.
Quoting from ThoughtWorks Technology Radar:
Clinic.js Bubbleprof represents visually the async operations in Node.js processes, drawing a map of delays in the application's flow. We like this tool because it helps developers to easily identify and prioritize what to improve in the code.
Let's see how the bubbles look like for a LoopBack application!
$ clinic bubbleprof --debug --autocannon $URL -- node .
Warning: The code is transpiled, bubbleprof does not support source maps yet.
Server is running at http://127.0.0.1:3000
Running 10s test @ http://localhost:3000/todo-lists?filter=(...)
10 connections
(...)
2k requests in 10.06s, 1.28 MB read
Analysing data
Generated HTML file is file:///(...)/todo-list/.clinic/42229.clinic-bubbleprof.html
You can use this command to upload it:
clinic upload .clinic/42229.clinic-bubbleprof
There isn't much to see in the report, the application spent less than 20ms waiting for async operations. I guess that was kind of expected considering that our example application is using in-memory storage and not communicating with any external service.
The last item in Clinic.js toolbox is Flame. Quoting from Clinic's website:
(...) flamegraphs are a visualization of profiled software, allowing the most frequent code-paths to be identified quickly and accurately. Clinic.js Flame is specifically for Node.js and is built into Clinic.js. It collects metrics using by CPU sampling, then tracks top-of-the-stack frequency and creates flamegraphs.
Can we find anything interesting in the flames? Let's see.
$ clinic flame --debug --autocannon $URL -- node .
Server is running at http://127.0.0.1:3000
Running 10s test @ http://localhost:3000/todo-lists?filter=(...)
10 connections
15k requests in 10.03s, 9.44 MB read
Analysing data
Generated HTML file is file:///(...)/todo-list/.clinic/42454.clinic-flame.html
You can use this command to upload it:
clinic upload .clinic/42454.clinic-flame
There is a lot of information packaged into a flame graph. Initially, Clinic.js is selecting the hottest frame - the stack that was observed most often while running the application. In our case, the hottest frame is pointing to Node.js internals dealing with writing data to streams.
There isn't much we can do about Node.js streams. Our application is writing HTTP responses so it has to spend some time writing the data. The third hottest frame is parseQueryStringValues
from the module qs
, this is again not something we can easily improve. Let's remove all components except dist
in the check-box list at the bottom of the screen to focus on LoopBack code only.
Now the list of hot frames looks much more actionable! Here are the top entries:
handle
in sequence.js
was 11.4% of time on top of stackbuildLookupMap
in relation.helpers.js
was 6.3% of time on top of stackfindByForeignKeys
in relation.helpers.js
was 5.7% of time on top of stackAnd so the list goes on. The entry number 7 caught my attention: the application is spending 1.3% of the time in generateName
from context.js
. That's suspicious, why is generating context names so expensive?
It turns out we are generating unique context names to make it easier to debug binding-related issues. Our initial implementation was based on UUID version 4 (random) algorithm. Universally unique ids are great when you are aggregating debug logs from a mesh of microservices. However, most of the time, context names are just a hidden property of context instances that's not surfaced anywhere. Applications shouldn't be paying performance tax for something they don't use. Let's see if we can find a better solution.
What other options are there to generate unique ids?
Let's compare the performance of these alternatives. I wrote a simple benchmark which you can find in this Gist and measured the following data:
method | ids/second |
---|---|
numeric counter | 11095k |
hyperid | 10234k |
UUID v1 | 1649k |
UUID v4 | 325k |
Initially, I wanted to use a numeric counter because it has the best performance. However, that would mean losing uniqueness of context names. At the end, I decided to go with hyperid
. It combines UUID v4 with a counter to achieve almost the same performance as the counter-only algorithm while preserving universal uniqueness of the generated ids.
To better measure the impact on performance, I created a small REST application that does not parse the query string and returns the response data directly, without going through @loopback/repository
. Replacing uuid/v4
with hyperid
improved the performance of my test application from ~2.4k
requests/second to ~3.5k
requests/second on average. The average request latency has improved from 3.8ms
to 2.6ms
.
The pull request loopback-next#5628 improving generateName
performance has been already landed and will become publicly available in the next framework release.
As the popular saying goes, performance is not a problem until it becomes a problem. Even if your project is not performance sensitive, it's still good to pay attention to how much resources it consumes at runtime.
Watch for warning signs. Maybe an existing test has suddenly started to fail on a timeout? When that happens, try to take the hard path: investigate the problem, dig deep to find the root cause and fix it.
Measure, measure, measure. Always create a reproducible benchmark to measure the effect of the changes you are going to make. Performance tuning is full of surprises and not all changes are for better.
Know the tooling. Node.js is compatible with many tools provided by Chrome's Developer Tools, it provides also tracing functionality for a more fine-grained analysis. You can learn more in Node.js documentation, starting from Debugging - Getting Started, Easy profiling for Node.js Applications and Flame Graphs.
Optimize hot paths. A semi-expensive function can become a performance problem when it's called for every incoming request.
LoopBack's success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Here's how you can join us and help the project:
]]>The completion of the migration epic would be the biggest news in May. Now LoopBack 3 users can find the migration guide here. Meanwhile, we have feature contributions and bug fixes happened across all the functional areas.
There are more than 20 community PRs merged in May and we really appreciate every community member's help. We set up community calls every four weeks to keep in touch with our maintainers. See the latest schedule and recording in this story.
Keep reading to learn about what happened in May.
The migration guide for components, which is a powerful way to contribute any artifacts, is the last but most widely covered story in the migration epic. To make the migration guide easier to navigate, we split component-related instructions into several sub-sections as "project layout", "models, entities and repositories", "current context", "model mixins", "REST API endpoints". You can check the documentation migration-extensions-overview and its sub-pages to learn the details.
When a LoopBack 3 application is mounted in a LoopBack 4 project, its endpoints are exposed through the LoopBack 4's REST server. To reuse the existing LoopBack 3 tests, you can easily migrate them by following the instructions in example lb3-application
. It covers how to set up clients to test requests and how to test runtime functions.
LoopBack CRUD operations invoke toObject
function internally to return a model instance. toObject
converts a value to a plain object as DTO (Data transfer object). It returned a JSON representation before, which doesn't preserve the prototype of complicated types like Date
, ObjectId
but returned the value as a string instead. Now such values' prototypes are kept, for example:
const DATE = new Date('2020-05-01');
const created = await repo.create({
createdAt: DATE,
});
// The returned model instance has `createdAt` as type Date
expect(created.toObject()).to.deepEqual({
id: 1,
createdAt: DATE,
});
LookBack 4 leverages Express behind the scenes for its REST server implementation. We decided to not use Express middleware as-is but now we support integrating the middleware in different ways. You can invoke it explicitly in the sequence, or register it to be executed by InvokeMiddleware
action, or use it as controller interceptors.
Page Express middlware explains all the scenarios and usages. And page Middleware provides the general knowledge of LoopBack 4 middleware.
Function createBindingFromClass
allow bindings to be created from dynamic value provider classes, for example:
@bind({tags: {greeting: 'c'}})
class DynamicGreetingProvider {
static value(@inject('currentUser') user: string) {
return `Hello, ${this.user}`;
}
}
// toDynamicValue() is used internally
// A tag `{type: 'dynamicValueProvider'}` is added
const binding = createBindingFromClass(GreetingProvider);
ctx.add(binding);
A provider class can use dependency injection to receive resolution-related metadata such as context and binding. But the overhead to wrap a factory function is not desired for some use cases. PR#5370 introduces a lightweight alternative using toDynamicValue as follows:
import {ValueFactory} from '@loopback/context';
// The factory function now have access extra metadata about the resolution
const factory: ValueFactory<string> = resolutionCtx => {
return `Hello, ${resolutionCtx.context.name}#${
resolutionCtx.binding.key
} ${resolutionCtx.options.session?.getBindingPath()}`;
};
const b = ctx.bind('msg').toDynamicValue(factory);
A benchmark is added to measure the performance of
different styles of context bindings in package @loopback/benchmark. You can run npm run -s benchmark:context
to see the result.
PR#5378 introduced a model booter to automatically bind model classes to the application during boot. You can retrieve and inject model constructors using key models.<model_name>
. For example:
@model()
class MyModel extends Model {}
class MyModelComponent {
models = [MyModel];
}
// you can get MyModel by `models.MyModel`
const modelCtor = myApp.getSync<typeof MyModel>('models.MyModel');
We upgraded the dependency to TypeScript@3.9.2. Code adjustments including null
check and type intersection were made to be compatible with the new version. You can check PR#5041 for more details.
Replace eslint rule no-invalid-this
with TypeScript-aware one: In code accessing this
variable, eslint-ignore comment for no-invalid-this
will no longer work. You can either
change those comments to disable @typescript-eslint/no-invalid-this
, or better tell TypeScript what is the type of this
in your function.
A TypeScript example:
import {Suite} from 'mocha';
describe('my mocha suite', function(this: Suite) {
this.timeout(1000);
it('is slow', function(this: Mocha.Context) {
this.timeout(2000);
});
})
A JavaScript example:
describe('my mocha suite', /** @this {Mocha.Suite} */ function() {
this.timeout(1000);
it('is slow', /** @this {Mocha.Context} */ function() {
this.timeout(2000);
});
})
Remove hand-written index files: We removed the root level dummy index files and changed the entry point of project to be the index file inside src
folder. An example of the latest layout of a package can be found in the Todo application.
You can register a booter to boot a sub-application as:
class MainAppWithSubAppBooter extends BootMixin(Application) {
constructor() {
super();
this.projectRoot = __dirname;
// boot a sub-application `app`, its bindings will be added as well
this.applicationBooter(app);
}
}
LoopBack is a framework built on top of Express. It comes packed with tools, features, and capabilities that enables rapid API and micro-services development and easy maintenance. Last month we published a blog summarizing the points that make LoopBack a compelling choice for Express developers when it comes to API development. You can read this blog to see how LoopBack can bring Express to the next level.
LoopBack 4 application can integrate with API Connect framework. We've prepared an article on how you can take the APIs created from LoopBack and import them into API Connect for API management. Stay tuned for the published article.
Documentation setting debug string explains the usage of running a LoopBack 4 application with debug string turned on. You can check the documentation above to learn the debug string pattern and the format in each package.
As a dependency of @loopback/rest
, package strong-error-handler
is an error handler for use in both development (debug) and production environments. You can use it to customize the error rejection in the LoopBack 4 sequence. For its detailed usage, please read the documentation using string error handler.
We've been sharing the connector documentation with LB3, which might be confusing, especially for new LB4 users. We updated the PostgreSQL connector page and also the tutorial. By walking you through the steps of creating a LB4 application and connecting to the PostgreSQL database, we hope the new tutorial helps new users to pick up LoopBack 4 better.
You can read page connecting to PostgreSQL to follow the tutorial.
For creating tutorials, we have more materials than documentations. Last month, one of our core maintainers Miroslav published two video tutorials on our StrongLoop YouTube channel:
There are several documentation and user experience improvements happened this month to make the authentication system more automatic and easy to use:
Added example @loopback/todo-jwt
to demo enabling JWT authentication in the Todo application. Its corresponding tutorial JWT authentication tutorial is coming soon.
Added security specification enhancer in @loopback/authentication-jwt to automatically bind the JWT scheme and global security specification to application. You don't need to manually add them in the application constructor anymore. The updated usage is documented in the README.md file.
This month, we would like to work on the remaining items for the migration guide epic, documentation improvement and more. For more details, take a look at our June milestone list on GitHub.
In 2020, we look forward to helping you and seeing you around! LoopBack's success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Here's how you can join us and help the project:
When building a LoopBack 4 application, we often need to tweak or improve the default data access behavior provided by the framework. It's usually desirable to apply the same set of customizations for multiple models, possibly across several microservices. In this post, I'd like to share a few tips and tricks for reusing such repository code.
In this approach, you insert a new repository class (the Repository Base Class, e.g. AuditableRepository
) between your model-specific repository class (e.g. ProductRepository
) and the repository class provided by the framework (typically DefaultCrudRepository
). The base class will hold any code you want to reuse in multiple model-specific repositories.
A week ago, I recorded a screencast showing the concept of Repository base classes in practice, you can watch it here:
The first step is to create a new source code file and implement an empty Repository Base Class. It's important to use .repository.base.ts
suffix in the file name, this will allow lb4 repository
to recognize the file as contributing a base class.
// src/repositories/auditing.repository.base.ts
import {
DefaultCrudRepository,
Entity,
juggler,
} from '@loopback/repository';
export class AuditingRepository<
T extends Entity,
ID,
Relations extends object = {}
> extends DefaultCrudRepository<T, ID, Relations> {
// put the shared code here
}
You should also add an entry to src/repositories/index.ts
file to re-export the new class:
// src/repositories/index.ts
export * from './auditing.repository.base';
When you run lb4 repository
command now, it will find our new base class and offer it in the prompts:
$ lb4 repository
? Please select the datasource DbDatasource
? Select the model(s) you want to generate a repository for Product
? Please select the repository base class (Use arrow keys)
❯ DefaultCrudRepository (Legacy juggler bridge)
----- Custom Repositories -----
AuditingRepository
I will not go into details on implementing custom persistence behavior here, please watch the screencast to learn how to create a repository class that sets the model property modifiedBy
to the currently authenticated user on every write operation.
Once you have the repository base class implemented, you may want to share it between multiple projects (e.g. microservices). I recommend creating a LoopBack 4 extension providing the base class and packaging the extension as a standalone npm module..
lb4 extension
src/repositories/auditing.repository.base.ts
file to the extension (you can use the same file name and path, i.e. src/repositories/auditing.repository.base.ts
)src/repositories/index.ts
and src/index.ts
to re-export (new) artifacts.In order to use the repository base class from the extension in an application project, we have a bit of work to do. At the moment, lb4 repository
does not scan dependencies in node_modules
for repository base classes. To make the base class discoverable by LoopBack's CLI, you can add a tiny wrapper file to your application into a location discoverable by the CLI. Implementation-wise, the wrapper just re-exports the base class provided by the extension.
// src/repositories/auditing.repository.base.ts
export {AuditingRepository} from 'my-extension-name';
That's it! Now you can easily create new model-specific repositories using lb4 repository
and select your shared repository as the base class.
While easy to use, Repository Base Classes have few shortcomings too.
JavaScript does not support multiple inheritance, thus it's not possible to combine behavior from multiple repository base classes in the same model-specific repository class.
Inheritance-based reuse is considered to be an anti-pattern in Object Oriented Design; it's recommended to use composition instead ("prefer composition over inheritance").
Let's take a look on how to use Mixins to share bits of repository code via composition.
Instead of creating a repository base class, we will create a repository mixin using the mixin class pattern.
import {MixinTarget} from '@loopback/core';
import {CrudRepository, Model} from '@loopback/repository';
export function AuditingRepositoryMixin<
M extends Model,
R extends MixinTarget<CrudRepository<M>>
>(superClass: R) {
return class extends superClass {
// put the shared code here
};
}
Because lb4 repository
does not support repository mixins yet, you have to edit model repository classes manually to apply your new mixin.
import {Constructor, inject} from '@loopback/core';
import {DefaultCrudRepository} from '@loopback/repository';
import {DbDataSource} from '../datasources';
import {AuditingRepositoryMixin} from '../mixins/auditing.repository-mixin';
import {Product, ProductRelations} from '../models';
export class ProductRepository extends AuditingRepositoryMixin<
Product,
Constructor<
DefaultCrudRepository<
Product,
typeof Product.prototype.id,
ProductRelations
>
>
>(DefaultCrudRepository) {
constructor(@inject('datasources.db') dataSource: DbDataSource) {
super(Product, dataSource);
}
}
We are discussing CLI support for repository mixins in loopback-next#5565, please leave a comment to let us know if you are interested in this feature.
Mixins are easy to share via LoopBack extensions too:
lb4 extension
src/mixins/auditing.repository-mixin.ts
file to the extensionsrc/mixins/index.ts
and src/index.ts
to re-export (new) artifacts.import
statements to import the shared repository mixin from the extension.When using a repository base class, it's easy to apply all project-specific behavior via a single base class. We can build a composite mixin to achieve the same easy of use with mixins too.
Let's say we already have AuditingRepositoryMixin
and TimeStampRepositoryMixin
implemented, and now we want to create MyProjectRepositoryMixin
that will apply those two mixins, so that repository classes in our project don't have to repeat the list of mixins to apply.
// src/mixins/my-project.repository-mixin.ts
export function MyProjectRepositoryMixin<
M extends Model,
R extends MixinTarget<CrudRepository<M>>
>(superClass: R) {
return AuditingRepositoryMixin(TimeStampRepositoryMixin(superClass));
}
Now you may be thinking: can we define a repository base class that would be recognized by lb4 repository
and would apply all required mixins? Unfortunately, the answer is NO.
Consider the following code:
// src/repositories/base.repository.base.ts
export class BaseRepository<
T extends Entity,
ID,
Relations extends object = {}
> extends MyProjectRepositoryMixin<
T,
Constructor<DefaultCrudRepository<T, ID, Relations>>
>(DefaultCrudRepository) {
// empty class
}
TypeScript reports the following error during compilation:
error TS2562: Base class expressions cannot reference class type parameters.
You can learn more about this problem and the reasoning for the current compiler behavior in GitHub issue Mixin does not allow Generic.
In this post, I explained how to extract bits of Repository code into a reusable form and how to share them by creating a new LoopBack extension. We discussed two options: an inheritance-based approach that uses Repository Base Class and a composition-based approach that uses Repository Mixin. Along the way, we discovered a few areas where TypeScript and LoopBack could improve the developer experience.
I hope you will find these techniques useful.
LoopBack's success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Here's how you can join us and help the project:
Express is the most popular Node.js package for web server development. Its lightweight, extensible, and flexible nature makes it a perfect fit for projects, small and large, from simple websites to complex web frameworks.
LoopBack is a framework built on top of Express. It comes packed with tools, features, and capabilities that enables rapid API and microservices development and easy maintenance.
In this post we will explore the points that make LoopBack a compelling choice for Express developers when it comes to API development.
First off, let's make it clear that Express and LoopBack are not mutually exclusive. You can very well use an existing Express app or middleware with LoopBack. This capability enables gradual migration from Express to LoopBack, that way you don't have to throw away your existing code and re-write everything from scratch.
To use an existing Express app with LoopBack, you can mount the LoopBack app on your Express app.
For a tutorial on how to do that, refer to "Creating an Express Application with LoopBack REST API".
LoopBack provides three broads ways for loading Express middleware.
The mountExpressRouter()
method of the RestApplication and RestServer class mounts an express router or application at a path, and supports OpenAPI specification for describing the endpoints provided by the router. It is the preferred choice for mounting data endpoints, like an existing REST API app.
For more details refer to "Mounting an Express Router".
Express middleware can also be plugged into the Sequence class using the invokeMiddleware()
action. This approach is recommended when you are not looking beyond a hard-coded list of middleware, when it comes to flexibility and configurability.
Refer to "Use Express middleware within the sequence of actions" for more details.
Express middleware can act as interceptors to controller methods at global, class, or method levels. It is not as simple as the previous two methods, but it provides the most configurability.
The following helper methods from the @loopback/express
package enable Express middleware to be wrapped into LoopBack interceptors.
toInterceptor
- Wraps an Express handler function to a LoopBack interceptor functioncreateInterceptor
- Creates a LoopBack interceptor function from an Express factory function with configurationdefineInterceptorProvider
- Creates a LoopBack provider class for interceptors from an Express factory function with configuration. This is only necessary that injection and/or change of configuration is needed. The provider class then needs to be bound to the application context hierarchy as a global or local interceptor.Refer to "Middleware" and "Use Express middleware as interceptors for controllers" for more information.
Express is a very extensible but bare-bones web server implementation. What LoopBack offers on top of Express is a set of tools and capabilities that make rapid API and microservices development possible and maintenance easy. REST API and microservices development with Express is possible but after a certain level of complexity, it can become a bug-ridden repetitive exercise for each new project. Using a REST API framework like LoopBack cuts down the development time and reduces maitenance headache.
Here are some of the points that makes LoopBack an excellent API development framework for Express developers.
LoopBack is specially crafted for REST API development. The framework's architecture, developer experience, and everything around it are designed primarily with REST API on mind.
MVC is a popular software design pattern that seprates the internal representation of data, implementation of access to this data, and what is presented to the client. This enables clear decoupling of the components that make up the application, which in turn leads to fewer bugs and better management of the development process.
LoopBack implements the MVC pattern. The models are defined in model files, controllers provide the REST API interface, and views are JSON objects returned by the controller. This not only allows modular development of the project, but also prevents the codebase from getting messy and unmanageable as the project grows.
Repository pattern is an abstraction for data access logic. It is a great way to decouple data access details from models.
In LoopBack, model files define only the shape and properties of models, connection and queries are handled by repositories which are bound to the models.
LoopBack uses OpenAPI specification for describing the data request and response formats. This highly descriptive standard specification greatly reduces the friction involved in the structural aspect of API development and consumption.
LoopBack exposes an OpenAPI specification file created out of the controllers in the app, which is essentially the documentation of the whole REST API of the app.
With a datasource defined and configured, once a model and its corresponding repository and controller are created, a CRUD functionality is automatically available for the model without any additional work.
The auto-generated functionality and implementation can be modified by editing the controller and/or the respository files of the model.
Configuring database connectivity and executing queries is one of the most crucial tasks when developing APIs. With the numerous database options available, writing optimal queries, and maybe even switching to a different database altogether can become a very tedious and time-consuming task.
LoopBack provides an abstraction for database access using datasources. All you have to do is select the database you want to use for your app and provide the connectivity details. LoopBack then takes care of making the connection and running the queries in the context of a REST API implementation.
Any time you want to switch to a different database, it is just a matter to speciying a new datasource. You don't have to worry about re-writing the queries, LoopBack takes care of it for you.
The following datasources are supported by LoopBack: In-memory db, In-memory key-value, IBM Object Storage, IBM Db2 (for Linux, Unix, Windows), IBM Db2 for i, IBM Db2 for z/OS, IBM DashDB, IBM MQ Light, IBM Cloudant DB, Couchdb 2.x, IBM WebSphere eXtreme Scale key-value, Cassandra, gRPC, Redis, MongoDB, MySQL, PostgreSQL, OracleDB, Microsoft SQL, and z/OS Connect Enterprise Edition.
Non-database datasources supported by LoopBack includes: OpenAPI, REST services, SOAP webservices, Email, and ElasticSearch.
Community supported datasources includes: Couchbase, Neo4j, Twilio, Kafka, and SAP HANA.
This wide of array of datasources covers almost all the popular databases used for REST API development, which significantly reduces the development time and effort in the database department.
Apart from using the non-database datasources provided by LoopBack, you can create your own services for connecting to external REST/SOAP/gRPC APIs.
These services can then be used in the controllers, effectively creating an OpenAPI-compliant proxy to those remote services. This usage scenario is perfect for proving a custom interface to an existing (legacy) API.
Conventionally, dependencies are passed as function parameters. This method works fine if the dependency is used only in the invoked function, however it can get pretty complex and unwieldy in certain scenarios because the dependency parameter is a factor that prevents the caller and the called function from being loosely coupled.
Imagine, a dependency is used within a function within a function within a function within a function. You will need to pass the dependency from the called function to the next function to the next function to the next function. Now imagine, the dependency has been changed to a different object in one or more places. You will now have to change it in the "top" function and all the places where it was being passed around. It is a mess.
Enter dependency injection (DI). Dependency injection enables dependent code to inject the dependencies themselves, instead of relying on the caller function to pass the dependency in function arguments. That way, any time something changes, it never includes the caller. The caller and called functions are loosely coupled.
LoopBack's Context object is a DI container. It makes it possible to inject dependencies in classes, properties, and methods without having to pass dependecies in constructor or method parameters.
The ability to use DI in your codebase can greatly improve the overall quality of code, increase development productivity, improve tests cases, and reduce maintenance costs.
LoopBack is designed to be highly extensible. It provides extensibility using different artifacts and patterns in different layers of the framework.
The LoopBack Sequence class contains the whole request-response handling infrastructure of the framework, therefore the Sequence
is the perfect place for implementing functionality that requires access to the beginning and the end of the request-response cycle - like logging, authentication, etc.
It is very easy to modify the existing functionality or add new ones by implementing a custom SequenceHandler for your app's Sequence
. The Sequence
file is located at src/sequence.ts
.
Components are great for grouping different but related artifacts for implementing a feature or functionality in the app.
Components can contribute the following artifacts to the app:
Extension points and extensions are the interfaces for developing plugins for LoopBack apps. It is an excellent pattern for adding decoupled extensibility to a software system.
LoopBack provides the following helper decorators and functions for implementating extension points and extensions on top of its Inversion of Control and Dependency Injection container.
@extensionPoint
- decorates a class to be an extension point with an optional custom name@extensions
- injects a getter function to access extensions to the target extension point@extensions.view
- injects a context view to access extensions to the target extension point. The view can be listened for context events.@extensions.list
- injects an array of extensions to the target extension point. The list is fixed when the injection is done and it does not add or remove extensions afterward.extensionFilter
- creates a binding filter function to find extensions for the named extension pointextensionFor
- creates a binding template function to set the binding to be an extension for the named extension point(s). It can accept one or more extension point names to contribute to given extension pointsaddExtension
- registers an extension class to the context for the named extension pointLife cycle observers are artifacts that can take part in the starting and stopping processes of the application. They can execute code as the app is starting (such as configuring something) or is shutting down (such as closing the connection to a server).
The LoopBack REST API server is just one of the many possible server capabilities of LoopBack. LoopBack can start multiple servers together of similar or different implementations, making LoopBack an excellent microservices hub. You can use this to create your own implementations of REST, SOAP, gRPC, MQTT and more protocols. For an overview, see Server.
LoopBack supports Interceptors. They are reusable functions to provide aspect-oriented logic around method invocations. There are many use cases for interceptors, such as:
a. Add extra logic before / after method invocation, for example, logging or measuring method invocations. b. Validate/transform arguments c. Validate/transform return values d. Catch/transform errors, for example, normalize error objects e. Override the method invocation, for example, return from cache
For more details about extensibility in LoopBack, refer to "Extending LoopBack 4".
Authentication and authorization form the basis of securing and controlling access to protected resources, and is a requirement for any app that deals with protected data. Authentication is responsible for verifying the user's identity before allowing access to a protected resource. Authorization is responsible for deciding if a user can perform a certain action on a protected resource or not.
LoopBack comes with an authentication component, which enables developers to plug in different authentication strategies (custom and standard) to the app. It also supports all the Passport authentication strategies.
You can read more about authentication in LoopBack in the authentication doc.
LoopBack's authorization component is a highly configurable authorization system, which allows you to write your own authorization rules or use an existing one.
All the details about authorization in LoopBack can be found in the authorization doc.
Datasources, models, controllers, repositories are great for modularizing the app, but manually creating the files and writing repetitive code with minimal differences for each new entity would be a tedious time-consuming activity.
LoopBack comes with a utility command-line tool, lb4. It has commands for generating datasources, models, controllers, repositories, and other LoopBack artifacts so that you don't have to create them manually.
Given a datasource, the discover
command can generate models files from the database. This can be a great time saver if you are using an existing database of your LoopBack app, and especially if the database had a lot of tables with many columns (or their equivalent structures). If an OpenAPI specification is provided, the openapi
command will not only create the model files, it will also create the controller files. This can save even more time.
Refer to the documentation for all the details about the lb4
command.
LoopBack is a TypeScript framework. TypeScript is a typed superset of JavaScript.
Although not directly a LoopBack feature, using a typed language for development prevents many bugs and steers developers towards using optimized coding practices. This can make a significant difference in the development and maintenance efforts when compared to using plain JavaScript.
LoopBack is an open source project backed by IBM, used by IBM products and customers. Hopefully, this gives you assurance on the quality and the longevity of this framework.
In 2020, we look forward to helping you and seeing you around! LoopBack's success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Here's how you can join us and help the project:
In April, we focused mostly on completing migration activities, like the migration guide and other related tasks like running existing tests in a LoopBack 3 application after composing it within a LoopBack 4 application. But, that didn't stop us from exploring and adding some cool features.
We now have a new Express package, which enables modeling Express middleware functions as an interceptor chain. Also it is possible now to break a complex application into much smaller components and wire them in a main application. You can find more details on thsese below in Exploring new territories
.
Also our community has published many LoopBack 4 extensions in NPM. Many of these extensions are addressing a variety of usecases like pub-sub messaging, mqtt, graphql, rate-limiting, authentication, logging, AWS cloud integration, etc. The extensibility of LoopBack in real time use cases are even surprising us and the possibilities seems to be endless.
We have made very good progress with migration guides and LoopBack 3 users should have a solid ground now to explore and migrate to LoopBack 4. The well used LoopBack 3 components are all covered with migrations examples and tutorials. There are certain components which are having fewer downloads per day, that are not covered yet. But we are pursuing steadily to address all migration questions.
We have created a document describing the differences between the request-response cycle in LoopBack 3 and LoopBack 4. Those of you coming from LoopBack 3 will have a better understanding about how the request-response cycle works in LoopBack 4 compared to LoopBack 3.
The LoopBack 4 request-response cycle documentation contains the details in more depth for LoopBack 4.
A new Passport Login example is now available. It shows how to use Passport Strategies in LoopBack 4 as an independent authentication step in the application Sequence
as well as standard express middleware. If you are using the loopback-component-passport in LoopBack 3, this example can help you migrate your application to LoopBack 4.
Because of the architectural differences, the booting process is very different in LoopBack 3 and LoopBack 4. This document describes the differences and lists the various artifacts that take part in the booting process in LoopBack 4.
The data coercion and validation and access to data before writing to the databases sections of the LB3 to LB4 request-response migration guide deals with the topic of access and application of custom validation to data in Loopback 4.
The command line interfaces of LoopBack 3 and LoopBack 4 have some similarities, but also some differences. We have outlined these similarities and differences in Migrating CLI usage patterns.
The new Express Package, has enabled injecting single and multiple express middleware functions as interceptors
into Controller
invocations and also as a middleware step in the application Sequence
as follows:
The default sequence now has a Middleware step. It creates an invocation chain to call registered middleware handlers with the extension pattern. The sequence can be customized to have more than one Middleware step. Express middleware can also be wrapped as LB4 interceptors, which can in turn be added to global/class/method level. Move built-in cors and openapi endpoints as express middleware functions.
You can check the express middleware page in loopback docs.
In story #3959 we explored the possibility and evaluated the required effort to migrate module loopback-component-oauth2
. Considering that LoopBack 4 currently focuses on the integration with third party OAuth 2.0 providers, and the module is complicated, we decide to defer the migration guide and demo a simplified server with OAuth 2.0 enabled on it.
You can find details about the mock server on page migration-auth-oauth2.
With users being able to mount their LoopBack 3 tests on a LoopBack 4 project, we explored how they can also migrate their LB3 tests onto the LB4 project. Documentation is coming, but if you want to see how an example of how to do it now, see the spike. The spike demonstrates running LB3 tests in the lb3-application
example.
Users can now break down a complex application into much smaller components and wire them all together in a main application, with a new feature to Boot up Component Applications.
The LoopBack APIConnect extension is now tested by publishing shopping app APIs, enhanced with the extension, on a IBM DataPower Gateway.
We took the shopping example for a close-to-real-life scenario. This would help IBM APIConnect customers to develop their applications with LoopBack and manage them with IBM APIConnect.
Once LoopBack developers have their REST APIs created they could use the LoopBack APIConnect extension to enhance their OpenAPI spec with x-ibm-
OpenAPI metadata. For the shopping example, we followed the steps in the example repository to deploy to IBM Cloud and then imported the OpenAPI specification to APIConnect with steps explained in the IBM developer portal.
After creating the demo for JWT authentication in loopback4-shopping-example
, and applied a similar auth system in access-control-migration
, we think it's time to extract the JWT authentication system into a separate extension package, so that people can quickly mount a component to try out the feature.
Last month, we created the extension as authentication-jwt, and its usage is well documented in the README.md file.
Strong-Soap now supports validation of anonymous simple types and RPC suffixes.
As many community users show the interests in changing the look of explorer, we introduced a configuration property called swaggerThemeFile
to specify user provided .css themes. For example:
// Inside application constructor
// customize the swagger-ui
this.configure(RestExplorerBindings.COMPONENT).to({
swaggerThemeFile: '/theme-newspaper.css',
});
You can check the complete guide in section customizing Swagger UI theme.
To align with existing typescript files and dynamic configuration of datasources, we have switched datasource configurations to .ts files from LoopBack 3 style json files. Please watch the video tutorial from Miroslav on migrating Migrate LoopBack 4 datasource config to TypeScript
.
LoopBack monorepo was configured in a hacky way to allow TypeScript to build individual packages. We have made changes to leverage TypeScript's Project-References. Project references are a new feature in TypeScript 3.0 that allow to structure TypeScript projects into smaller pieces.
Changes done to make default compilation target as ES2018 and enable all ES2020 features for lib
configuration.
A list of AJV features have been added in the past few months including AJV keywords, AJV extensibility, AJV service provider and asynchronous validations.
In LoopBack 4, models describe the shape of data, repositories provide behavior like CRUD operations, and controllers define routes. It's easy to manipulate and query data with LB4. However, for a long time LoopBack 4 documentation was missing the Woring with Data section and users were referencing the old docs in LoopBack 3. Even though LB3 has almost the same querying rules as LB4, the different styles between LB4 and LB3 sometimes are still causing confusion.
Gladly, we added that section with different filters under the page Usage Scenarios - Working with Data. For each filter, we introduced the basic usage with Node.js and REST APIs and also show examples of using both APIs. For instance, we have an example of showing how the limit
filter works with Node.js API and also the corresponding example of using REST.
Node.js API:
await orderRepository.find({limit: 5});
REST:
/orders?filter[limit]=5
If you want to connect to a REST service with an OpenAPI description, the OpenAPI connector would be what you need. We updated the documentation in the Calling other APIs and web services to include this usage. Besides, we added more configuration details in the OpenAPI connector docs page.
Autogenerated API docs had descriptions empty for all packages which now fixed by adding ts docs to all packages. Please take a look at the API docs to see the difference.
LoopBack users will be able to automatically extract schemas used in multiple places into #/components/schemas
and replace the references with a $ref
, with a new OAS enhancer.
This month, we would like to work on the remaining items for the migration guide epic, documentation improvement and more. For more detials, take a look at our May milestone list on GitHub.
In 2020, we look forward to helping you and seeing you around! LoopBack's success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Here's how you can join us and help the project:
In the past, we've explored a few options on providing a forum for our users to help each other: Google group, Gitter and GitHub. We are pleased to announce that the LoopBack Slack workspace, https://loopbackio.slack.com/, is available for our users to join. Since Slack is quite commonly used, we thought it would be a good time for us to modernize our tooling for the LoopBack community helping out each other out. Also, the LoopBack core team uses Slack on a daily basis; it is helpful because it allows us to get notifications and communicate efficiently.
There have been lots of great questions and answers. We thought it would be helpful to curate some of the discussions here. Thanks again for submitting the questions and answers!
Question: I am trying to find a working implementation for TimeStamp Mixin to have time stamp automatic fields in the database. In the older version of LoopBack, I was capable to create a BaseEntity and BaseRepository and to extend them but now it is not working anymore. If I extend in the same way the controllers are not working anymore. The current example in the docs is based on adding the mixin to the Controller which I like much less. Any suggestions? Thanks.
Answer: For specifying the creation timestamp, you can use the default
property for the @property
decorator in your model. Something like:
@property({
type: 'date',
default: () => new Date(),
})
createDate: string;
You can also use Moment.js to format the timestamp.
Updating updatedAt field should be possible via 2 ways:
@model
decorator) and then extend it where necessary.Question: Kinda new to loopback, I want to learn more about decorators and how to custom loopback logic for more advanced usages, can you walk me through the process of creating custom decorators to create my own "hook" around a controller?
Answer: A great starting point would be the Extending LoopBack 4 docs.
These concepts are the building blocks of LB4. They serve a specific purpose while following the OOP paradigm. It may look like a lot, but these are essentially the different extension points in LoopBack 4 (hence why LB4 is extremely extensible). Let's see if we can break it down:
Decorators (in general) The decorators in LB4 are no different to the standard decorators in TypeScript. They add metadata to classes, methods. properties, or parameters. They don't actually add any functionality, only metadata. Think of it like the file properties on your file system: It's not visible when interacting with the file normally, but those who want to access those properties will be able to via a standard interface. There's more benefits to Decorators, but the above explanation is the watered-down gist of it.
Sequence (in general) Sequences are a group of Actions. It simply indicates which actions should be used by the server to process the request.
Sequence Actions (in general) Sequence Actions (or simply "Actions") are stateless, meaning that they only have the basic concept Elements. Converting into Express.js terminology; Think of an Action as an middleware. And think of an Element as the contents that a middleware receives. They work differently, but the high-level idea is about the same. They are unaware of other higher-level concepts such as Controllers, DataSource, Models, etc.
Components (in general) When adding functionality to LB4, you'll usually need to add a combination of Providers, Booters, etc. This can tedious to manage. Hence, Components are registered once in the LB4 Application, which will then register the other stuff for you. @authenticate Adds authentication metadata.
AuthenticationComponent A component to register the necessary artifacts.
AuthenticationActionProvider This is a Sequence Action. Essentially, it adds an "authentication" step to the Sequence.
AuthenticationStrategyProvider This is a standard interface that the @loopback/authentiation package understands. Hence, any authentication strategy that adopts this interface can be used in @loopback/authentication. Think of it like the standard interface for Passport.js uses to interface with many different authentication strategies
Question: I have experience with other ActiveRecord implementations. If I was able to utilize TypeORM, this would be more straightforward. You mentioned TypeORM is coming soon as an option for LoopBack 4?
Answer: You can track progress of a proof of concept here: https://github.com/strongloop/loopback-next/pull/4794 Loopback 4 has been designed to allow flexibility so you can for example use TypeORM if you prefer.
Question: I am using mysql connector, I have generated models using LB4 model, But when I migrate the models from loopback to database using npm run migrate
. The foreign key constraints were missing in database. I have many.model.ts files. How to have foreign key in database with npm run migrate.
Answer: AFAIK, you’ll need to add some settings in the @model
decorator on the FK configuration so that npm run migrate can pick up.
I’ve tried that for postgresql using this snippet. Hope it works for you for mysql as well.
There is an GitHub issue tracking the work to add constraints in db migration: https://github.com/strongloop/loopback-next/issues/2332.
Question: Can anyone point me in the right direction on how to do loggig in LB4?
Answer: You have lots of options. If you want to do it inside of the context of the loopback application, with IoC binding, you can create a singleton service provider that returns the log utility of your choice. For example, with winston:
// services/logger.service.ts
import { bind, BindingScope, Provider } from '@loopback/core';
import * as winston from 'winston';
import * as Transport from 'winston-transport';
@bind({ scope: BindingScope.SINGLETON })
export class LogService implements Provider<winston.Logger> {
logger: winston.Logger;
constructor() {}
value() {
if (!this.logger) {
const transports: Transport[] = [];
transports.push(
new winston.transports.File({
handleExceptions: true,
format:winston.format.json(),
filename: '/path/t'
}),
);
this.logger = winston.createLogger({
transports,
exitOnError: false,
});
}
return this.logger;
}
}
application.ts
// in constructor
this.bind('loggingKey').toProvider(Logger).inScope(BindingScope.SINGLETON);
controller.ts (also applies for service.ts and others)
export class HelloWorldController {
@get('/hello-world')
public async getHelloWorld(
@inject('loggingKey') logger: winston.Logger
) {
logger.info('logging to a file!');
return 'Hello World';
}
}
With binding and injection, you can do some pretty cool stuff, like this extension that gives you a @log(LOG_LEVEL.INFO)
decorator that can be used to time a request:
https://github.com/strongloop/loopback-next/tree/master/examples/log-extension
There's also the old school nodejs way of just importing a file that exports a log utility, all set up in the global scope. I believe most tutorials for utilities like winston start with that :)
Question: Are there any solution to see the errors of model in the response of the request?
Answer: See https://loopback.io/doc/en/lb4/Sequence.html#handling-errors for reference.
Simply click this invitation link to join. You can also find more channel details here: https://github.com/strongloop/loopback-next/issues/5048.
]]>As LoopBack 3 is expected to reach its EOL by the end of this year, we have been working hard to achieve feature parity between LoopBack 3 and LoopBack 4. One feature of LoopBack 3 that we did not have in LoopBack 4 yet was the ability to go directly from only a model definition and model configuration to fully-featured CRUD REST API. Unlike LoopBack 3, LoopBack 4 relied on intermediate repository and controller classes in order to go from a model defintion class to use REST API. One thing that LoopBack 4 strives to do is make common tasks as easy as possible, while allowing advanced composition with loosely-coupled artifacts. So, after completing tasks from the related epic, we are now proud to announce that LoopBack 4 now offers support for going from a model definition to REST API with no custom repository or controller classes.
In LoopBack 4, the model definition provides the schema and the datasource configures how to access the database. Starting with these two artifacts, the user can directly expose REST API by using the following CLI command:
lb4 rest-crud
For example, if you have a model Product
and datasource db
, you can use the command as follows:
lb4 rest-crud --model Product --datasource db
The command can also take in multiple models at the same time. You can find more information on how to use the command in the REST CRUD generator documentation.
What the command does is it creates a configuration file describing properties of the REST API:
/src/model-endpoints/product.rest-config.ts
import {ModelCrudRestApiConfig} from '@loopback/rest-crud';
import {Product} from '../models';
module.exports = <ModelCrudRestApiConfig>{
model: Product, // name of the model
pattern: 'CrudRest', // make sure to use this pattern
dataSource: 'db', // name of the datasource
basePath: '/products',
};
Then it adds CrudRestComponent
from @loopback/rest-crud
to the application:
src/application.ts
import {CrudRestComponent} from '@loopback/rest-crud';
this.component(CrudRestComponent);
Documentation for this feature can be found in Creating CRUD REST APIs from a model.
We implemented @loopback/rest-crud
based on the @loopback/model-api-builder
package. This model API builder is what builds CRUD REST API from the model definition and datasource.
To demonstrate this functionality with an example, we added a new example based on the Todo
example. @loopback/example-rest-crud
mimics the behavior of the Todo
example, but does not include any custom repository or controller classes like the Todo
example. To download this example, use the following command:
lb4 example rest-crud
While the main epic is now complete, there are additional out of scope tasks that are part of future work. If you would like to contribute, please see the following issues:
The LoopBack team appreciates all your contributions!
In 2020, we look forward to helping you and seeing you around! LoopBack's success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Here's how you can join us and help the project:
The past few weeks have been challenging for many of us. While this pandemic situation affected our lives in different ways, we hope you all stay safe.
In Q1, we continued to make good progress in the following areas:
Let's take a closer look.
Building the migration guide is one of the key focuses for us this year. We made good progress in completing the migration guide. Please check out our migration guide. LoopBack 3 is currently in maintenance LTS. Read this blog to find out what it means and how it affects you.
In LB3, it was possible to use mixins to enhance a model with a new property, a custom method, or a custom remote method. In LB4, this can be accomplished by using a mixin class factory function against model, repository, or controller classes. We added a Migrating model mixins page to demonstrate how to accomplish this.
In LB3, Operation hooks are useful tools that are triggered by all methods that execute a particular high-level CRUD operation. However, LB4 hasn't supported this feature yet. To help LB3 users to continue using the feature, we provide a workaround and explain how they can migrate LB3 operation hooks to LB4 repositories in page Migrating operation hooks.
LB3 has the built-in User/AccessToken model based authentication. In LB4, we provide a more flexible authentication system. We explain how LB3 users can migrate it to LB4 with handy LB4 CLI tools. The content can be found in page Migrating built-in authentication.
In addition, we added the LB3 features not in-plan documentation page to clarify which LB3 features are not supported in LB4 or the workarounds for those features if users would like to continue using them in LB4.
Besides documentation, we migrated the LoopBack 3 access control example to LoopBack 4. In the lb3-application example, we added instructions on how to move the middleware from the LoopBack 3 application to a common location where both the LB3 and LB4 applications can use it.
For tooling that helps your migration process easier, the lb4 import-lb3-models
command now supports migrating models inheriting from all other models, including LoopBack 3 built-in models.
One of the frequent inputs we got from users is that they would like to see fewer steps from creating the models to having runnable endpoints. We now have the rest-crud
package, the app booter, and the CLI command. You can see how to create a simple LoopBack 4 app with the lb4 rest-crud
CLI command.
To glue these pieces together and helper users to understand the feature, we added a page Creating CRUD REST APIs. You can also run this CRUD REST example.
Earlier, we identified the work items required for the integration with IBM API Connect. When importing an OpenAPI spec generated by a LoopBack 4 application, there is additional metadata needs to be added. Instead of having our users to modify the OpenAPI spec manually, we introduced an API Connect OpenAPI enhancer to inject the x-ibm-configuration
and other required attributes in the OpenAPI spec.
Additionally, we extracted and translated the messages for our CLI tooling.
We continue to add examples which demonstrate commonly used scenarios. With the growing number of examples, we also categorized the Example list. Here are the newly added examples:
access control migration example to show how to migrate a LoopBack 3 application with access control to LoopBack 4.
file transfer example for exposing APIs to upload and download files using Express Multer.
validation example for adding different kinds of validations in a LoopBack 4 application.
rest-crud example for using CrudRestComponent
to define repository and controller classes for a model without creating those classes
If there's any example you'd like to see, feel free to open an issue in the loopback-next repo. Better yet, submit a PR and contribute!
LoopBack 4 is designed to be extensible. We added three extensions in this quarter for various usages:
@loopback/extension-logging provides logging facilities based on Winston and Fluentd.
@loopback/apiconnect is the IBM API Connect OpenAPI enhancer extension extends LoopBack with the ability to integrate with IBM API Connect.
@loopback/cron provides integration with Cron so that applications can schedule jobs using cron
based schedule.
The IBM Db2 for i connector was added to the connector list. You can now conveniently create a IBM Db2 for i datasource using our CLI. If you are starting a new project that connects to IBM Db2 for i, we recommend you to use this connector instead of loopback-connector-db2iseries
connector. You can find more details in the Db2 for i connector page.
In this quarter, thanks to our community contributors, we had a number of important enhancements in the OpenAPI area. An OASEnhancerService
was introduced which allows other enhancers to register and contribute OpenAPI specs into the application. This also provides the base for the API Connect OpenAPI enhancer mentioned above. On top of that, a few OpenAPI convenience decorators were added, for example, the @deprecated()
decorator and the @tags(tagNames: string [])
decorator for a class and method.
For the core of the framework, we have added the support hot-loading of controllers during application startup. Besides, the context and binding inspection APIs were improved with more options and information to print out their injections. More details can be found in the February milestone blog.
The most noticeable changes for our shopping example is the newly added frontend. There was some rework on the authentication and authorization side to make the app working from end to end.
Last but not least, if you haven't noticed already, our documentation site has a refreshed look. Don't forget to check it out!
With increasing number of user testimonials, we created a separate page for it. Let us know if you would like to tell us about your LoopBack usage!
There are many more accomplishments that cannot be captured in this blog, make sure you check out our previously published monthly milestone blog posts in Q1 for more details:
We have published a blog LoopBack - 2020 Goals and Focus about our plans this year. Here is a summary of the Q2 2020 roadmap:
In 2020, we look forward to helping you and seeing you around! LoopBack's success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Here's how you can join us and help the project:
The whole world has been through a lot in the past month. The LoopBack team hopes that everyone stays safe and gets through this together.
Let's check out the work we did in March:
As LoopBack 3 will go end of life at the end of 2020, we've been focusing on the migration guide for the past months. Here is the content we added in March to help LB3 users adopt LoopBack 4:
In LoopBack 3, the authentication system is a token-based one and has built-in models involved in the mechanism. In LB4, we built a more flexible authentication system that is compatible with different authentication strategies. Even though there are lots of differences, the newly created access control migration example explores how to migrate and build an equivalent LoopBack 3 authentication system in LoopBack 4 with detailed steps. The tutorial includes two main parts:
The tutorial also uses the handy LB4 CLI to help LB3 users to get familiar with LB4 terms. Read the migration-authentication tutorial to learn about the details.
Besides migrating artifacts from LB3, there are several features/components we no longer support anymore in LB4. They are listed in the page LoopBack 3 features not planned in LoopBack 4. We also provide workarounds for these features if users would like to continue using them in LB4.
The story From Model to REST API with no custom repository/controller epic is almost done! In the past few months, we created the @loopback/rest-crud
package, as well as the the ModelApiBooter
booter. And this month, we built the CLI command lb4 rest-crud
. To glue these pieces together, we added an example and documentation to help you pick up this convenience tool. Details are listed below. We will have a blog post in the near future.
In order to make it easier for users to use this feature, we've added a CLI command to simplify the process. If you have model classes and a valid(persisted) datasource, the following command will generate model endpoints for you:
lb4 rest-crud
We've added a new rest-crud
example which creates the Todo
example without the need to define a repository or controller for the Todo model. By loading the CrudRestComponent
, it demonstrates how to use the default CRUD REST repository and controller with a single model class , datasource, and configuration. The example can be downloaded by running:
lb4 example rest-crud
You can find more information on how to use the command in the REST CRUD generator documentation.
Now that most of the epic is completed, we've added documentation explaining how to use the feature and the configuration options that come with it. Additionally, we also added documentation on extending the @loopback/model-api-builder
package to create your own custom model API builders; similar to @loopback/rest-crud
's CrudRestApiBuilder
.
We've been adding more examples to show what you can build with, and how you can configure a LoopBack 4 app. One of our favorite examples is the Shopping App. It shows how you can integrate LB4 APIs with a simple front-end design to build a site. Besides the rest-crud
example mentioned above, we added more examples to show various LoopBack 4 features.
LB4 allows you to add validations at three different layers: REST, controller, and ORM. The newly added documentation Validation explains these three different types of validations. We added a corresponding example Validation Example to our Examples list demonstrating how to add and make use of different kinds of validations in a LoopBack 4 application.
Uploading/downloading files is a common requirement for API applications. The documentation for Upload and download files shows the code snippets to create artifacts such as controllers and UI to achieve such a requirement. A fully-functional example is available at File Transfer Example.
We made some changes in the layout design of the website. Hope you like the new look!
To help users have a better understanding of all the components involved in the request-response handling process, in the Request-Response cycle document, we walk through the path taken by a request to see how it makes its way through the various parts of the framework to return a result. In the near future, we will also add documentation in the migration guide to explain the differences of the request-response cycle between LB3 and LB4. See the GH story Migration Guide: Request-response cycle for more details.
We made the CHANGELOG easier to find on our site. It is available in the section CHANGELOG. We hope it helps developers to check out the changes of different packages for each release.
We're glad to see a growing number of user testimonials. We refactored it in a new page. Check out the what our users say section. Let us know if you would like to tell us about your LoopBack usage!
The IBM Db2 for i connector was added to the connector list. You can now conveniently create an IBM Db2 for i datasource using our CLI. See the Db2 for i connector page for more details.
Here are the extensions we added to the framework:
The IBM API Connect OpenAPI enhancer @loopback/apiconnect extension was added to extend LoopBack with the ability to integrate with IBM API Connect.
An experimental extension @loopback/cron
was added. With it, LB4 apps can be integrated with Cron to schedule jobs using cron
based schedules.
After creating the demo for JWT authentication in loopback4-example-shopping and applying a similar system in loopback-example-access-control, we think it's time to extract the JWT authentication system into a separate component. This will benefit users who want to quickly mount a prototype token based authentication module to their application. As the first step, we extracted the JWT strategies, the token, and user services into a local module under components/jwt-authentication. Next we will move it to a standalone extension package. Feel free to join the discussion in GH story Extract the jwt authentication to an extension module.
Model property of type any
is now supported. The corresponding OpenAPI and JSON schema is {}
or true
(according to the draft JSON schema standard). If your model property allows arbitrary values, now you can define it as:
class MyModel extends Entity {
// ...other code
@property({
// specify the type name here as 'any'
type: 'any'
})
// use `any` as its TypeScript type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
anyProperty: any
}
We fixed a bug in module @loopback-ibmdb
where a put request PUT /Model/{instanceId}
now operates correctly. The fix trickles down into any LoopBack connector with a dependency on @loopback-ibmdb
like @loopback-connector-db2
and @loopback-connector-dashdb
, for example.
We fixed a bug in connector @loopback-connector-mssql
which was causing permission problems during installation on Windows. Some extra folders ended up in the package tgz file, and this was causing the problem. The fix went out for several LoopBack connectors: MSSQL, DB2, dashDB, Cloudant, MongoDb, MySQL, Oracle, PostgreSQL, and Redis KeyValue.
Our community maintainers and users have been very helpful with building a better LoopBack 4, we really appreciate all the help! Here are the highlights this month:
The community maintainer dougal83
improved the authentication strategies AuthenticationStrategy
so that it can be bound with the OAS enhancer extension point via a binding key instead of a constant.
The community user saotak
added several LB4 pages in Japanese. See the site. We need your help to have more translations for the LB4 documentations! The instructions can be found in the page Translation.
In 2020, we look forward to helping you and seeing you around! LoopBack's success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Here's how you can join us and help the project:
The February in a leap year is quite special and we hope everyone has some memorable stories from that extra day! In the past month, LoopBack team continued to focus on the migration guide epic. In the meantime, we were able to contribute significant PRs across all the functional areas. We are really glad to see the increasing engagement from community members, we appreciate all your code reviews and contributions. Last but not least, we published new major releases for @loopback/*
modules as as we dropped Node.js 8 support and introduced a few other breaking changes.
Keep reading to learn about what happened in February!
While we work on a spike for supporting operation hooks for models/repositories, we are providing a temporary API for enabling operation hooks in LoopBack 4. It requires overriding the DefaultCrudRepository
's definePersistedModel
method in the model's repository.
Here is an example of adding a before save
operation hook to the Product
model.
class ProductRepository extends DefaultCrudRepository<
Product,
typeof Product.prototype.id,
ProductRelations
> {
constructor(dataSource: juggler.DataSource) {
super(Product, dataSource);
}
definePersistedModel(entityClass: typeof Product) {
const modelClass = super.definePersistedModel(entityClass);
modelClass.observe('before save', async ctx => {
console.log(`going to save ${ctx.Model.modelName}`);
});
return modelClass;
}
}
For more details visit Migrating CRUD operation hooks.
The initial implementation of lb4 import-lb3-models
was able to import only models inheriting from models that have a built-in counter-part in LoopBack 4: Model
, PersistedModel
, KeyValueModel
. Now it also supports migrating models inheriting from all other models, including LoopBack 3 built-in models like User
, or an application-specific model. The chain of base (parent) models will also be created in the LoopBack 4 application. For example, model Customer
extends model UserBase
which extends model User
, and if you run lb4 import-lb3-models
, you will see the following prompts:
$ lb4 import-lb3-models ~/src/loopback/next/packages/cli/test/fixtures/import-lb3-models/app-using-model-inheritance.js
WARNING: This command is experimental and not feature-complete yet.
Learn more at https://loopback.io/doc/en/lb4/Importing-LB3-models.html
? Select models to import: Customer
Model Customer will be created in src/models/customer.model.ts
Adding UserBase (base of Customer) to the list of imported models.
Model UserBase will be created in src/models/user-base.model.ts
Adding User (base of UserBase) to the list of imported models.
Model User will be created in src/models/user.model.ts
Import of model relations is not supported yet. Skipping the following relations: accessTokens
Ignoring the following unsupported settings: acls
create src/models/customer.model.ts
create src/models/user-base.model.ts
create src/models/user.model.ts
update src/models/index.ts
update src/models/index.ts
update src/models/index.ts
As the first story to explorer the authorization migration path, we started with migrating a LoopBack 3 example application which implemented a RBAC (role based access control) system for demoing the LoopBack 3 authentication and authorization mechanism.
The migrated LoopBack 4 example is created in examples/access-control-migration. It uses casbin as the third party library to implement the role mapping. The original models and endpoints are migrated to the LoopBack 4 models, repositories, and controllers. The JWT authentication system is applied again and the core logic of original role resolvers and model ACLs map to the LoopBack 4 authorization system's authorizers and metadata.
We created a very detailed tutorial for the migration steps that you can follow to see how to secure the same endpoints in LoopBack 4.
We've added a section Migrating model mixins to the migration guide to detail how LoopBack 3 property and custom method/remote method mixins can be migrated to LoopBack 4 model/repository/controller mixin class factory functions.
We have confirmed that migration also passes down the connector metadata in the model properties with additional tests.
@loopback/extension-logging
contains a component that provides logging facilities based on Winston and Fluentd. Here is an example of injecting and invoking a Winston logger:
import {inject} from '@loopback/context';
import {Logger, logInvocation} from '@loopback/extension-logging';
import {get, param} from '@loopback/rest';
class MyController {
// Inject a winston logger
@inject(LoggingBindings.WINSTON_LOGGER)
private logger: Logger;
// http access is logged by a global interceptor
@get('/greet/{name}')
// log the `greet` method invocations
@logInvocation()
greet(@param.path.string('name') name: string) {
return `Hello, ${name}`;
}
@get('/hello/{name}')
hello(@param.path.string('name') name: string) {
// Use the winston logger explicitly
this.logger.log('info', `greeting ${name}`);
return `Hello, ${name}`;
}
}
Its architecture diagram and basic usage are well documented in the package's README.md file.
Context and binding inspection APIs were improved with more options and information to print out their injections.
At binding level, there is one flag:
includeInjections
: control if injections should be inspected.An example usage is:
const myBinding = new Binding(key, true)
.tag('model', {name: 'my-model'})
.toClass(MyController);
// It converts a binding with value constructor to plain JSON object
const json = myBinding.inspect({includeInjections: true});
At context level, there are two flags:
includeInjections
: control if binding injections should be inspected.includeParent
: control if parent context should be inspected.And their corresponding example usages:
childCtx.inspect({includeInjections: true});
childCtx.inspect({includeParent: false})
More test cases can be found in PR https://github.com/strongloop/loopback-next/pull/4558
@raymondfeng has created loopback4-example-inspect to demonstrate the inspection of a LoopBack 4 application's context hierarchy. It provides visualization on the different contexts (request, server, application), their bindings, and dependency injections in class constructors. Information is exposed via 3 endpoints:
This example is turning into an extension @loopback/context-explorer
in PR #4666. The core code is packed as a component.
The hot-reloading of controllers after starting application is supported now. You can dynamically add/remove controllers after the application runs, and their endpoints will be mounted/removed accordingly. The OpenAPI specification that describes the exposed endpoints will also be updated. For example:
const app = new Application();
await app.start();
app.controller(MyController);
// MyController are available via REST API now
// You can also see the updated OpenAPI Specification from endpoint /openapi.json
lb4 discover
CLIThe CLI now allows selection of two naming convention for lb4 discover
command: camel case or all lower case. You can find the explanation of each prompt in the Discovering models from relational databases page. discoverAndBuildModels
allows you to have different conventions to meet your requirements. Details can be found in page Discover and define models at runtime.
We added a new API builder that helps build a CRUD repository and controller class in PR #4589. CrudRestApiBuilder
can be used with an Entity
class to create a default repository and controller classes for the model class.
For example, if you have a Product
model and a database db
. In your src/application.ts
file:
// add the following import
import {CrudRestComponent} from '@loopback/rest-crud';
export class TryApplication extends BootMixin(
ServiceMixin(RepositoryMixin(RestApplication)),
) {
constructor(options: ApplicationConfig = {}) {
// other code
// add the following line
this.component(CrudRestComponent);
}
}
Create a new file for the configuration, e.g. src/model-endpoints/product.rest-config.ts
that defines the model
, pattern
, dataSource
, and basePath
properties:
import {ModelCrudRestApiConfig} from '@loopback/rest-crud';
import {Product} from '../models';
module.exports = <ModelCrudRestApiConfig>{
model: Product,
pattern: 'CrudRest', // make sure to use this pattern
dataSource: 'db',
basePath: '/products',
};
Now your Product model will have a default repository and default controller class defined without the need for a repository or controller class file.
For more information on the API builder, see @loopback/rest-crud
's README.
We simplified the filter
and where
usage for constraint, schema, and OpenAPI mapping with two shortcut decorators: @param.filter
and @param.where
. The example below shows how they replaced the tedious signatures:
class TodoController {
async find(
@param.filter(Todo)
// replaces `@param.query.object('filter', getFilterSchemaFor(Todo))`
filter?: Filter<Todo>,
): Promise<Todo[]> {
return this.todoRepository.find(filter);
}
async findById(
@param.path.number('id') id: number,
// replaces `@param.query.object('filter', getFilterSchemaFor(Todo))`
@param.filter(Todo, {exclude: 'where'}) filter?: FilterExcludingWhere<Todo>,
): Promise<Todo> {
return this.todoRepository.findById(id, filter);
}
async count(@param.where(Todo) where?: Where<Todo>): Promise<Count> {
// replaces @param.query.object('where', getWhereSchemaFor(Todo)) where?: Where<Todo>,
return this.todoRepository.count(where);
}
}
We have now changed the OpenAPI specification generated by the decorator @param.query.json
to support url-encoding. Please take a look at (https://github.com/strongloop/loopback-next/issues/2208). This means users can now test their APIs from API explorer with complex json query parameters (eg: filter={include: {relation: "todoList"}}
). Previously users were able to test from API explorer with only simple key-values in exploded format (eg: ?filter[limit]=1
), because the generated OpenAPI for json query parameters was always of exploded deep-object
style. This could be a breaking change for some API clients. Please take a look at the breaking change log in commits from PR https://github.com/strongloop/loopback-next/pull/4347
npm test
is passing on Windows: The problem was caused by process.stdin.isTTY
behaves differently on the Windows platform, and was discovered by community member @derdeka. Great thanks to him and @dougal83 who had been working with @bajtos to investigate and eventually fix the issue! A series of PRs are involved: #4643, #4605, #4652, #4657
PR #4707 removed dist files from top-level tsconfig to speed up the eslint checks. The time for npm run eslint
was reduced from about 4m10s down to 2m50s. More importantly, it enabled proper caching behavior, so that subsequent runs of npm run eslint
are super quick, even after npm run build
modified dist files.
Dropped Node.js 8 support in PR #4619. Node.js v8.x is now end of life, so that we upgraded the supported version across all the LoopBack 4 packages to be 10 and above. This breaking change also resulted in a semver-major release for the monorepo. Many small breaking changes are coming as part of it.
request
module is now officially deprecated, so we replaced it with a new HTTP client axios
. The entire story is tracked in #2672. We have updated the http-caching-proxy and benchmark packages to use axios.
We upgraded the dependency of TypeScript from 3.7 to 3.8 in PR #4769. You can find the new features of TypeScript 3.8 in here
Querying with filter where
, fields
and order
is now supported in the API Explorer, the usage is well documented in the section parameter decorator to support json objects
We enabled running shared tests from both loopback-datasource-juggler@3 and loopback-datasource-juggler@4 in one more connector: loopback-connector-db2
We fixed a bug in postgresql connector which occurred when few of the foreign keys in a parent table have null values (https://github.com/strongloop/loopback-next/issues/4332)
After refactoring the shopping example, we updated the README.md file to document the new changes of application usage and the authorization system.
We published two blog posts this month about the management and plan for our project:
LoopBack 3 has entered Maintenance LTS: https://strongloop.com/strongblog/lb3-entered-maintenance-mode/
The 2020 Goals and Focus for LoopBack: https://strongloop.com/strongblog/2020-goals/
With more LoopBack users joined us as community maintainers, we're seeing more interactions and discussions! Also, we're glad to see that the increasing numbers of pull request from the community. We really appreciate all of these help! Here are the highlight of community PR of February:
disableDefaultSort
to Improve Database Query PerformanceUser Erikdegroot89
pointed out that the way LB4 sets default sorting for SQL query might drag down the querying time when the database has a massive amount of data. Using the new added flag disableDefaultSort
, users can turn the default sorting off. See details in PR #417. This PR also inspires us to leverage the option to all connectors. The issue is tracked in GH issue. Feel free to contribute or join the discussion.
@oas.deprecated
was created by user mschnee to enrich our OpenAPI decorators. It can be applied to class and a class method. It will set
the deprecated
boolean property of the Operation Object. When applied to a
class, it will mark all operation methods of that class as deprecated, unless a
method overloads with @oas.deprecated(false)
. You can check out its documentation to learn more details.
In 2020, we look forward to helping you and seeing you around! LoopBack's success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Here's how you can join us and help the project:
2019 was surely an exciting and rewarding year for LoopBack. We received the "Best in API Middleware" award from API World, our LB4 downloads on npmjs.com increased more than double compared to the year before. Moreover, we are thrilled to learn that many of you had deployed LoopBack 4 applications in production! In case you missed our 2019 review, make sure you check it out. As 2020 begins, we would like to share with you our high-level goals for this year. Things might change or shift throughout the year, but here is our plan.
With LoopBack 3 going end-of-life at the end of 2020, we are continuing to focus on the following areas:
We had made good progress in the general runtime migration guide. As we complete the spike for the migration guide on authentication and authorization, we aim at finishing the migration guide in both areas. Our approach is to create the skeleton of the migration guide and fill in the details as we go. If you'd like to see certain migration topics but they are not in the guide, please let us know!
Migration tooling
To allow you to migrate your LB3 app incrementally, we have an example for you to demonstrate how to mount your existing LoopBack 3 (LB3) application on a new LoopBack 4 (LB4) project and how to move the middleware from the LB3 application to a common location so that both the LB3 and LB4 applications can use them. Besides, there is also a CLI to import models from LoopBack 3 project.
We would like to continue to add and enhance the migration tooling to make your migration experience easier and faster.
Feature parity
This year, we would like to close some of the feature parity gaps that a lot of you are asking for. We also realized that there are existing libraries to support certain functionalities in some cases, we'd like to document our recommendation as well.
Integration into IBM API Connect
With LoopBack 3 being packaged in IBM API Connect, it is also a good time for us to have LoopBack 4 integrated into the product. A spike has been done on the integration work and we'll continue to work on this.
While enhancing our core code base, we'd like to continue to explore the possibility of using and/or integrating the latest technologies with LoopBack 4, such as:
We initiated some investigation in some of the areas last year and would like to continue to adopting the latest technologies in order for LoopBack to stay relevant.
We cannot do this alone! Community contributions are vital to us. As our user base gets larger, it is essential to grow the contributor community. It not only accelerates our development, but also helps the community gain deeper LoopBack knowledge.
We would like to encourage community contributions by making a list of GH issues available to the community that we want to finish in the short term by using the help wanted
and 2020Qx
labels. If you're looking for something to contribute in LoopBack, please check our issue list for 2020Q1.
If you're new to contributing to LoopBack or in open source project in general, don't worry, we have detailed instructions to guide you through.
In 2020, we look forward to helping you and seeing you around! LoopBack's success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Here's how you can join us and help the project:
Almost a year ago, we announced Extended Long Term Support for LoopBack 3, extending Active LTS to the end of November 2019. As the saying goes, all good things must come to an end, and so LoopBack version 3 has entered Maintenance LTS in December 2019.
What does this change means for LoopBack 3 users? Quoting from our Long Term Support policy:
Once a release moves into Maintenance LTS mode, only critical bugs, critical security fixes, and documentation updates will be permitted.
Specifically, adding support for new major Node.js versions is not permitted.
Let's quickly clarify that Node.js 12 is the latest major Node.js version supported by LoopBack 3.
Now back to the first rule, which limits the allowed updates to critical problems only. This rule has two goals:
There is a catch though: because LoopBack 4 is fundamentally incompatible with LoopBack 3, there are bugs that exists in LoopBack 3 only. The usual approach, where bugs are fixed in the Current version and back-ported to LTS versions, cannot be applied. As a result, we are tracking several community-contributed pull requests fixing issues specific to LoopBack 3.
We feel it would be counter-productive to reject those in-progress pull requests now, after several rounds of reviews and adjustments, just because LoopBack 3 transitioned from Active to Maintenance LTS. We don't want to throw away effort invested by developers contributing those fixes and thus we decided an exceptional situation deserves an exception to be made.
Until June 2020, we will keep reviewing pull requests fixing non-critical bugs in LoopBack 3 and if we evaluate the risk of breaking something else as low, then we will accept the fix.
At the end of June, we will evaluate the impact of this new rule and decide if we want to extend its duration further.
The following packages are considered as part of LoopBack 3 and are moving to Maintenance LTS:
Please note that connectors are compatible with both LoopBack version 3 and version 4, therefore they are staying actively developed.
We urge all LoopBack 3 users to migrate their applications to LoopBack 4 as soon as possible. We are providing Migration guide and automated tooling to help with the transition.
If you are building a new project, then we strongly recommend to use LoopBack 4 from the beginning.
LoopBack's success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Here's how you can join us and help the project:
It's a Leap Year this year, and we continue to make leaps in LoopBack 4.
With New Year's resolutions in mind, we quickly got started on several items.
Check out the sections below for the progress we made in each area:
The LoopBack 4 example app now has a website - Shoppy.
Check out https://github.com/strongloop/loopback4-example-shopping/ and start the app; Shoppy is available at http://localhost:3000/shoppy.html.
This website serves as an example for integrating LoopBack 4 APIs to a front-end and as a basis for you to experiment with various LoopBack 4 features.
The authorization portion has also been revamped to make it easier to follow.
In LoopBack 3, predefined boot scripts are organized in the /server/boot
directory, and are executed right before the server starts to perform some custom application initialization. The same functionality can also be achieved in LoopBack 4 application by adding observers.
Having the observers created, you can access the application and artifacts like models, datasources by dependency injection or retrieving from the context. Moreover, specifying the order of observers is also supported. For the 2 pre-defined LoopBack 3 boot scripts (/server/boot/root.js
and /server/boot/authentication.js
), please do not create corresponding observers for them. In LoopBack 4, the router is automatically registered in the rest server and the authentication system is enabled by applying the authentication component. See Migrating boot scripts for more details.
In LoopBack 3, a remote hook enables you to execute a function before or after a remote method is called by a client. There are three kinds of hooks: global, model, and method. LoopBack 4 provides the interceptors feature to enable application developers to implement similar functionality. See Migrating remoting hooks for more details.
In LoopBack 3, developers could customize model methods in various ways:
configure which endpoints are public
customize the model method, but not the endpoint
add a new model method and a new endpoint
The first could be done by modifying some settings in server/model-config.json
or calling the disableRemoteMethodByName( methodName )
on the model. The second was accomplished by overriding a default model method inside the model script file. The third was accomplished by adding a new model method and a new remote method definition inside the model script file.
In LoopBack 4,
data-access APIs (model methods) are implemented by repositories that are decoupled from models.
REST APIs (remote methods) are implemented by controllers that are decoupled from models.
Migrating the LoopBack 3 model method customizations to LoopBack 4 is very straightforward. See Migrating custom model methods for more details.
A spike was completed which outlined the remaining tasks required for authentication and authorization migration details between LB3 and LB4. Please see PR #4440 for details.
The Command Line Interface (CLI) is one of the most convenient tools of LoopBack. With a few commands and basic information, it allows you to create a LoopBack application in a short time. We made some improvements in the following CLI commands to make them more intuitive and flexible (especially the relation
one):
lb4 relation
Alright, we admit that our lb4 relation
command wasn't entirely user-friendly -- it generated partial code even when you forced it to stop; it didn't handle customized names even when it looked like it would; it didn't support the HasOne
relation. We improved on some of these issues. As you can see in the newly updated Relation Generator page, it now takes customized foreign keys and relation names with more descriptive prompt messages. Need an example? We updated the TodoList Example with the latest CLI capabilities.
Even though the CLI is a handy tool and has a lot of functionality, it still has limitations. For instance, users can customize foreign key names, relation names, source key names, and even the database column names in relations. The newly released CLI changes currently supports some of these, but not all of them. For the latest details on defining relations, you can always check the Relations page.
As for defining a HasOne
relation through the CLI, one of our community users @Lokesh1197
has provided this new capability via PR #4171. See hasOne Relation and Relation generator for updated documentation.
lb4 openapi
LoopBack 4 uses index.ts
files to export different kinds of artifacts. The lb4 openapi
command wasn't generating/updating this file. We noticed this recently and fixed it immediately. Phew! Don't forgot to install the latest @loopback/cli
to get the patch!
If you are/used to be an LB3 user, you're probably familiar with the strict
mode. It allows you to create models that permit both well-defined and also arbitrary extra properties. LB4 has this nice feature as well. However, it is applicable to NoSQL databases only. If you applied this setting to SQL databases, it would get silently discarded and this made it difficult for developers to troubleshoot unexpected behavior.
Now, users will receive a warning when they try to set a model to {strict: false}
mode while they are using a SQL datasource in an LB4 application. We updated the Supported Entries of Settings table of the Model
page to clarify this potential issue.
We addressed some context-related performance degredation bugs that recently came in. It turns out that matching all bindings by a filter function can be expensive. PR #4377 addresses these bugs and improves performance for one of the primary usages of the context - find bindings by tags.
Performance was improved by:
We started to investigate the steps required, if needed, on importing OpenAPI specs generated from a LoopBack 4 application into IBM API Connect v2018. There are additional extended configurations and APIC product files that are needed in order to import the API successfully. As the next step, we will be testing all endpoints of our example shopping application with API Connect, and documenting the steps. For details on the spike, see https://github.com/strongloop/loopback-next/issues/4115.
@Lokesh1197
updated our relation CLI with the ability to define a HasOne
relation via PR #4171. See hasOne Relation and Relation generator for updated documentation.@achrinza
has made several small improvements/clarifications in our documentation.@dougal83
added the title property to filter schemas (filter, where, scope) in preparation for openapi schema consolidation. See PR#4355 for more details.A big 'Thank you!' to all our contributors! :)
isActive()
method. This allows you to determine if the connection object on the transaction is present or not. Suppose you have a transaction instance called tx
, you can call tx.isActive()
to check the activeness of its connection without throwing an error (if an error happens). See PR #4474 and PR #4537, and Using database transactions for details.@loopback/authorization
's README.md document to include detailed steps of implementing a basic RBAC system. See PR #4205 for details.strong-docs
's dependencies to use the latest TypeScript 3.7. See PR #128 for details.strong-globalize
. See PR #151 and PR #153 for details.If you're interested in what we're working on next, you can check out the February Milestone.
In 2020, we look forward to helping you and seeing you around! LoopBack's success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Here's how you can join us and help the project:
Happy New Year! The number of LoopBack 4 downloads in 2019 increased more than double than that in 2018. Thank you for your continuous support in using and contributing to LoopBack. We cannot do this alone and we really appreciate all the contributions from the community. In December, we were happy to have 3 community members join us as maintainers: @derdeka, dougal83 and achrinza!
As year 2020 commences, let us summarize our development activities in the last quarter of 2019.
Enriching the Migration Guide from LB3 story is our focus of this quarter and will continue to be the focus. Adding on top of the comparison between the concepts in LoopBack 3 and that in LoopBack 4, we created the skeleton on the areas that need more explanation in the migration. You can find it on our site: Migration Guide
In Q4, we added pages for migrating: Model, Datasource, Model Relation, Express Middleware, etc. If there are other topics you'd like to see in the migration guide, please let us know on GitHub.
In the past few months, we made significant amount of enhancement in the cloud native area. Not only we added the extensions for logging, health check, tracing and metrics, we also created the deployment to Kubernetes tutorial in our shopping example application. For details, take a look at the blog post from Raymond.
Besides, the Node.js LoopBack stack provides a powerful solution to build microservices in TypeScript with LoopBack. Appsody is an open source project that makes creating cloud native applications simple. It has many cool features which are pre-configured with cloud native capabilities for Kubernetes and Knative deployments. In our detailed Appsody with LoopBack Tutorial on developing and deploying LoopBack applications, we would like to show you the possibility and potential of how these kinds of tools can work well with LoopBack of building microservices.
We added the support for authentication and authorization in LoopBack 4. Check out the Authentication page and the Authorization page for the latest features. Want to try out a real-world example? We updated the shopping example application to use the authentication and authorization systems to help you get familiar with it.
Also, we made some progress on the story allow users to have token-based authentication in API Explorer in Q4. Starting with a spike as the blueprint, we now added an extension point for the OpenAPI enhancers as the first brick in the wall. Check out the "Extending OpenAPI Specification" page for details. As always, we'd love to get any help from you. Here are some follow-up stories if you're interested in contributing:
We finished the Inclusion of Related Models MVP in Q4! This addition not only simplifies querying data and reduces database calls in LoopBack 4, but it closes one feature gap between LoopBack 3 and LoopBack 4 as well.
In the past few months, we released a bunch of features such as custom scope for inclusion, and we added inclusion resolvers to lb4 relation CLI, etc. We enhanced the documentation with examples and usages along with a blog post to show how you can query data over different relations easily. Still, there are some limitations and unfinished tasks. Check Post MVP if you'd like to contribute.
Speaking of better performance and manageability of databases, the database that supports partitioning is one of the ideal choices. Are you considering to use databases that have the feature such as Cloudant and CouchDB with LoopBack? We now support such features in the corresponding connectors. It not only makes the query less computationally, but also reduces cost for LoopBack users using the Cloudant service on IBM Cloud. We have prepared a tutorial and documentation to help you get started! See the details and examples on the usage in Partition Databases.
As LoopBack 4 provides more scalability and extensibility, we ask users to create artifacts such as Model, Datasource, Repository, and Controller to start building their applications. Compared to LoopBack 3, it adds complexity and extra steps to create APIs. This story aims to improve the developer experience for those who may not need that extra flexibility.
You might wonder how simple it would be. In the spike, if you already have the database (we use MySQL in the spike) and tables set up, you can create basic CRUD APIs just through the API Explorer. For example, all you need to do is to make a POST request with a valid MySQL connection string and a list of existing tables,
{
"connectionString": "mysql://root@localhost/test",
"tableNames": [
"Coffeeshop"
]
}
then the new endpoints will be created for you.
Implementations are on the way! Feel free to try out the spike and join the discussion on GitHub :D
If you have been following us, you probably realize that we now start our planning of the milestones and roadmaps with a pull request. We think it is useful to our users to get to know our plans and possibly provide inputs in our planning stage. See our 2020 Goals and Focus and Q1 roadmap. There is also the Janurary milestone.
Check out our previously published monthly milestone blog posts in Q4 for more details:
If you want to see a 2019 summary, don't forget to check out this blog!
LoopBack's success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Please join us and help the project by:
It's 2️⃣0️⃣2️⃣0️⃣!
Happy New Year! Is the snow dancing outside of your window or is the sunshine bringing warmth and glow to the grass around you? No matter where you are, the LoopBack team is thankful for you being with us through 2019! It means a lot to us that you choose LoopBack for your applications and projects.
We're also excited to have Denny, Douglas, and Rifa as LoopBack maintainers! They've been actively helpful in our community. We appreciate all the contributions and great work. Welcome to the team!
Even though this past December was a short month due to the holidays, the list of the accomplished tasks is not short! Check out the sections below for the progress we made in each area:
@typescript-eslint
rules.Initially, LoopBack 4 required all artifacts (Model, Repository, and Controller classes) to be defined in TypeScript source files. Recently, we started to work on a declarative approach, where the Repository and Controller classes can be created dynamically at runtime (see @loopback/rest-crud
package).
This month, we pushed this concept one step further and implemented a proof of concept showing how to dynamically build CRUD REST API for any SQL database table:
ModelDefinition
object from the discovered schemaModelDefinition
objectThe demo application can be found in the pull request loopback-next #4235.
To make this scenario possible, we needed to make few improvements:
loopback-datasource-juggler
to make DataSource APIs like discoverSchema
easier to consume using await
keyword.defineModelClass
that builds a Model class constructor using the given base model (e.g. Entity
) and the given ModelDefinition
.As part of the experiment, we have again encountered the limitation of our REST layer when controllers registered after startup are not picked up. This feature is discussed in loopback-next #433. Feel free to chime in and perhaps contribute a pull request if this use case is important for your projects.
We introduced a new package @loopback/model-api-builder
for building APIs from models. This package allows users to build repositories and controllers based on their models through their defined extensions. We also added ModelApiBooter
that leverages Model API builders contributed via ExtensionPoint
/Extension
to implement the actual API building. See README file for details.
We managed to finish the Inclusion of Related Models MVP in 2019! Check out the Post MVP if you'd like to contribute.
Traversing data through different relations is a common use case in real world. Take a nested relation as an example, a Customer
might be interested in the Shipment
status of their Order
:
Customer: {
name: 'where\'s my order at'
orders: [
{
name: 'order 1',
shipment: {
shipment_id: 123
}
},
{
name: 'order 2',
shipment: {
shipment_id: 999
}
}
]
}
In PR #4263, we enabled such traversal by allowing users to customize the scope
field for their query filter. The above example can be achieved by the following query:
customerRepo.find({
include: [
{
relation: 'orders',
scope: {
include: [{relation: 'shipment'}],
},
},
],
});
More use cases and examples are added to the page Query Multiple Relations.
It's convenient to traverse related models with relations. However, when it comes to operations such as creation and updating, navigational properties might cause some unexpected problems. In PR #4148, we decided to reject CRUD operations that contain any navigational properties. For example, the request to create a Customer
with its Address
will be rejected:
customerRepo.create({
name: 'customer',
address: [
{
street: 'nav property',
city: 'should not be included',
},
],
});
We've added a convenience function interface named UserProfileFactory<U>
to @loopback/authentication
. Implement this interface with your own custom user profile factory function to convert your specific user model into a UserProfile model (used by both @loopback/authentication
and @loopback/authorization
).
The StrategyAdapter
in @loopback/authentication-passport
now takes in an additional argument userProfileFactory
in its constructor. This argument is initialized with a default implementation of the UserProfileFactory<U>
interface, mentioned above, and simply returns your specific user model as a user profile model. It is recommended that you implement your own user profile factory function to map a specific/minimal set of properties from your custom user model to the user profile model. Please see the updated documentation for more details.
Some interceptors want to check the caller to decide if its logic should be applied. For example, an http access logger only cares about invocations from the rest layer to the first controller. In PR #4168, we added an option source
, which can check the caller that invokes a method with interceptors. Check out the Interceptors page for relative documentation and examples.
It’s often desirable for various types of artifacts to participate in the life cycles and perform related operations. In PR #4145, we improved the the check for application states and also add the shutdown hooks which allows graceful shutdown when the application is running inside a managed container such as Kubernetes Pods. Please check the related documentation and examples on the site Life cycle events and observers to help you understand more about the LoopBack 4 application life cycle.
We've added a new extension point OASEnhancerService
to allow the OpenAPI specification (short for OAS) contributions to a rest application. The feature originated from the need to add security schemes and policies to a LoopBack application's OAS. Now you can modify your application's OAS by creating and registering OAS enhancers.
A typical OAS enhancer implements interface OASEnhancer
which has a string type name
field and a function modifySpec()
. For example, to modify the info
field of an OAS, you can create an InfoSpecEnhancer
as follows:
import {bind} from '@loopback/core';
import {
mergeOpenAPISpec,
asSpecEnhancer,
OASEnhancer,
OpenApiSpec,
} from '@loopback/openapi-v3';
@bind(asSpecEnhancer)
export class InfoSpecEnhancer implements OASEnhancer {
// give your enhancer a proper name
name = 'info';
// the function to modify your OpenAPI specification and return a new one
modifySpec(spec: OpenApiSpec): OpenApiSpec {
const InfoPatchSpec = {
info: {title: 'LoopBack Test Application', version: '1.0.1'},
};
const mergedSpec = mergeOpenAPISpec(spec, InfoPatchSpec);
return mergedSpec;
}
}
Then bind it to your application by this.add(createBindingFromClass(InfoSpecEnhancer))
.
The OAS enhancer service organizes all the registered enhancers, and is able to apply one or all of them. You can check "Extending OpenAPI specification" documentation to learn more about creating and registering OAS enhancers, adding OAS enhancer service and applying its enhancers.
We will allow users to have token-based authentication in API Explorer in the near future. Please check our future milestone blogs.
We have added a new model-property property persistDefaultValues
, which prevents a property value that matches the default from being written to the database when it set to false
.
This is particularly useful when you have a model with a lot of properties or sub-properties whose values may be the default value, and these models run into thousands or maybe millions. Setting persistDefaultValues
to false
can drastically reduce the write time and size of the database. This setting is applicable to LB4 and LB3. Check the document on our site: Property.
Auto-migration is a convenient tool to help you create relational database schemas based on definitions of your models. Typically, auto-migration would use the database's default type as the primary key type. For example, the default type of MySQL is integer, and the default type of MongoDB is string. We've added a field useDefaultIdType
before that allows you to use other types than the default type when doing auto-migration. For example, for MySQL, the following setting allows you to have a string type primary key in tables:
@property({
type: 'string',
id: true,
useDefaultIdType: false,
// generated: true -> can not be set
})
id: string;
However, sometimes users want to have auto-generate primary key with non-default type. For instance, a common use case is having uuid
as the primary key in MySQL or PostgreSQL. We enable the auto-generated uuid
with auto-migration for PostgreSQL. In short, the following setting enables auto-generated uuid
:
@property({
id: true,
type: 'string',
generated: true,
useDefaultIdType: false, // this is needed if this property is id
postgresql: {
dataType: 'uuid',
},
})
id: String;
By default, when the user wants to auto-generate string type properties in PostgreSQL, we use uuid
and the function uuid_generate_v4()
. It is possible to use other extensions and functions. Please check on the site for more details: PostgreSQL connector.
This feature will be added to MySQL in the near future.
We have enabled several new @typescript-eslint
rules to detect more kinds of potential programming errors. These new rules will trigger a semver-major release of the package @loopback/eslint-config
. Be prepared to handle new violations after upgrading.
List of new checks:
As part of this effort, we migrated our code base to use nullish coalescing and optional chaining operators.
Appsody is an open source project that makes creating cloud native applications simple. It provides application stacks for open source runtimes and frameworks, which are pre-configured with cloud native capabilities for Kubernetes and Knative deployments.
In August, LoopBack was added as one of the application stacks in Appsody to provide a powerful solution to build open APIs and Microservices in TypeScript. The name of the stack is nodejs-loopback.
Please refer to our tutorial Developing and Deploying LoopBack Applications with Appsody for detailed instructions on how to use the Appsody CLI to:
We enhanced the documentation and updated the tutorial of migrating LB3 application on a new LB4 app. By mounting the LB3 middleware with a base Express application, the middleware can be shared by both LB3 and LB4 apps. For example, you can use REST APIs in your LB3 app with a new LB4 application, which will be helpful if you are a LB3 user and ready to move to LoopBack 4. Check out Migrating Express middleware for the steps and examples.
To improve the documentation for migration from LB3 to LB4, we also added steps for migrating LB3 datasources into a LB4 project. LB3 datasources are compatible with LB4, so the datasource configuration remains the same between both. See Migrating Datasources for steps on how to migrate your datasources.
Typically, the primary key is used as the source key in relations (i.e joining two tables). In LB4, we use keyFrom
and keyTo
to define the source key and foreign key respectively. If you would like to use a non-id property as your source key, setting keyFrom
would allow you to do so. Check the Relation Metadata section for details.
We've added some links and refactored the Using Components page to make the site better navigation.
We've updated the Authorization Component page detailedly to match the latest code base.
Interested in joining a user feedback session? We love to hear your input and how you are using LoopBack. If you'd like to take part or have suggestions, please join our discussion in the GitHub issue.
If you're interested in what we're working on next, you can check out the January Milestone.
In 2020, we look forward to helping you and seeing you around! LoopBack's success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Here's how you can join us and help the project:
As 2019 draws to a close, we're continuing our annual tradition of celebrating the hard work that the LoopBack team achieved in the past year. As you might expect, developing LoopBack 4 was the main focus, with events, updates and "how to" content adding flavour to the mix. That focus has resulted in a lot of improvements and features for the framework, as well as quality interaction with the LoopBack community.
Grab your seasonal beverage of choice, whether hot or cold, and read on for a trip down 2019 memory lane!
2019 began with LoopBack just past the 12,000 star count on GitHub, and wrapped the year up at more than 13k. LoopBack 4 began the year at more than 1260 and practically doubled it by year's end at more than 2400. Meanwhile, npmjs.com shows 432,800+ downloads of LB4 since January 1 - quite a jump from the prior year's 141,948. We're excited to see all of this activity as LoopBack downloads continue to grow.
Congratulations again to LoopBack for earning the 2019 API Award for the “Best in API Middleware” category. These awards were presented at the 2019 API Awards Ceremony during API World 2019, celebrating the technical innovation, adoption, and reception in the API & Microservices industries and use by a global developer community. Raymond Feng, co-creator and architect for LoopBack, presented the team to receive the award.
Well done, LoopBack team!
With the team focusing so much on enhancing and imporving LoopBack 4, there were a lot of updates. In terms of the features, here are the highlights:
Authentication & Authorization
Model relations
Architectural improvements
Migration / Migration guide
Strengthen the core modules as a platform for building large-scale Node.js projects
Enhancements in connectors
Update Example Shopping app to showcase the features we’ve added
Experimenting with Plain javascript programming in LoopBack 4: https://strongloop.com/strongblog/loopback4-javascript-experience/
Improvement in documentation
Enabled Node.js 12 support, Added latest TypeScript 3.7 support, switch to ESLint, etc.
Want a bit more detail on some updates? Check out "Experimenting with Plain JavaScript Programming in LoopBack 4" by Hage Yaapa (which looks at functionality following a spike to enable LoopBack 4 development using JavaScript), "What's New in LoopBack 4 Authentication 2.0 by Dominique Emond, and "LoopBack 4 Offers Inclusion of Related Models" by Agnes Lin.
The LoopBack web site was changed from the LoopBack 3 look feel to the LB4 theme that was launched previously.
The team also refreshed the "Who's using LoopBack" section and added more testimonial from the users. If your company wants to be highlighted as the LoopBack user on our web site, please see strongloop/loopback-next#3047 for details.
We would like to provide more visibility to you (our users) on what we have accomplished and our plans, so we started to create blog posts to keep everyone up-to-date. We always welcome feedback!
Milestone github issues to show our plan for the month (github ticket with "Monthly Milestone" label.
Monthly milestone and quarter summaries on what we have accomplished (you can see a summary of what we posted in 2019 further into this post).
To go even further, we start our planning on milestone and a quarterly roadmap in pull requests. Look for pull requests with "Monthly Milestone" and "Roadmap" labels.
The LoopBack team managed to join some events throughout the year!
DEVELOPERWEEK 2019 ran from February 20-24, and LoopBack architect Raymond Feng covered "Speed and Scale: Building APIs with Node.js, TypeScript and LoopBack."
The Toronto Cloud Integration Meetup group held a Meetup in Toronto in February with the topic "Quickly Build APIs with Existing Services and Data Using LoopBack!” Janny Hou explained what LoopBack is, what you can do with it, and the rationale behind the rewrite of the framework. Biniam Admikew demonstrated how how easy it is to expose REST API from your database with just a few steps. Jamil Spain provided an additional demo while also taking care of capturing the meetup on video. Check out the details and video here.
LoopBack QuickLab - Code@THINK, NodeConf.eu and Node+JSInteractive.
HackerJS Meetup in California. Raymond Feng shared his cotent from DEVELOPER WEEK 2019 with this meetup group.
TechConnect (internal IBM event at the Canada Lab in Markham) - May 2019
Raymond also presented at a meetup in Santa Clara in May. "Building APIs at Warp Speed with LoopBack "
As mentioned earlier, Raymond Feng attended the 2019 API Awards Ceremony during API World 2019, in October to accept the 2019 API Award for the “Best in API Middleware” category.
In November, the LoopBack team attended CASCONxEVOKE. As one of Canada’s largest combined academic, research and developer conferences, it offered over 150 speakers to over 1,500 attendees. Diana Lau provided an overview of the LoopBack booth and a workshop. Learn more here.
In March, the LoopBack team announced LoopBack 3 was receiving an extended long term support to provide more time for users to move to the new version which is a different programming model and language. The revised LTS start is December 2019 and the revised end of life is December 2020.
Check out the timeline and some frequently asked questions here.
While much of their focus was on improving LoopBack 4, the team also shared insight into using the framework as well. Here's a look back!
Diana Lau shared a two part series about learning LoopBack 4 Interceptors. In the first part she looked at Global Interceptors, what they are and how to use them. In Part 2 she looked at Method Level and Class Level Interceptors, building an application that validates the incoming request using class level and method level interceptors.
Miroslav Bajtoš announced and demonstrated preview version of a tool automating migration of models from LoopBack 3 to LoopBack 4. Check it out here. In a similar vein, Nora Abdelgadir shared a way to mount your LoopBack 3 applications in a LoopBack 4 project in "Migrating from LoopBack 3 to LoopBack 4".
Wenbo Sun provided a 7-part series called "Building an Online Game With LoopBack 4". The aim of the series is to help you learn LoopBack 4 and how to use it to easily build your own API and web project. Wenbo did so by highlighting a new project he was working on: an online web text-based adventure game. Check the series out here.
For more details about work on LoopBack, the best resource is the monthly milestone updates. The LoopBack team outlined their progress with these updates, often explaining hurdles or rationale for changes and tweaks. Check them out below:
You can also follow LoopBack's progress throughout the year in the LoopBack 4 2019 Q1 Overview, LoopBack 4 2019 Q2 Overview, and LoopBack 4 2019 Q3 Overview.
Migration guide work began this year and it will continue to be the focus. The goal is to add some tooling around migration to make the migration process from LB3 to LB4 easier. If you have an existing LoopBack 3 application, take a look at the migration guide and gives us feedback!
The LB team will also work on feature parity that is needed for LB3 to LB4 migration, and the developer experience to make users' lives easier. They would like to take some time in each milestone to address some of the pain points that users mentioned. The team is also looking at innovation around cloud native deployment, database integration, as well as messaging/event driven style APIs.
Finally, look for improved documentation that address user questions, and more up-to-date docs that better reflect the quickly-changing code base.
Keep an eye out to see the developments in the new year!
]]>The LoopBack team greeted November with the CASCONxEVOKE conference in Toronto. CASCONxEVOKE is one of Canada's largest combined academic, research and developer conferences. As its speakers and attendees, we had a booth with posters to advocate LoopBack, and also delivered a workshop about developing extensible LoopBack applications. You can check this blog for more details.
For Q4 achievements, we finished 3 epics this month: Inclusion of related models, Deployment guide in a cloud native environment and Support partitioned database in Cloudant connector, and significantly progressed in the Migration, Authentication & Authorization epics.
Keep reading to learn about the recently added features!
To ensure our relations test suites work against real databases, we've been adding different kinds of databases to our test environment. This month we added a new repository@loopback/test-repository-cloudant
to run shared CRUD and relation tests on Cloudant. You can also set up docker instance easily with our setup script to test out your application. See PR#3968 for more details and try it out if you're interested.
Besides supporting inclusion in queries, we now also set constraints to CRUD operations with navigational properties to avoid unexpected errors. For example, if you try to create an instance of TodoList with all Todos it has through the hasMany relation such as:
todoListRepository.create(
{
id: 1,
name: 'my list 1',
todos:[
{id: 1, description: 'todo 1', todoListId: 1},
{id: 2, description: 'todo 1', todoListId: 2}, // incorrect foreign key
]
}
)
Such requests might be problematic because they might contain incorrect primary key or foreign key. Therefore, with such concerns, request contains navigational properties will be rejected. PR#4148 implements the verification for CRUD methods.
Additionally, in order to ensure that the correct metadata type is being using when it is resolved, we've added tests in PR#4046 and simplified our test setup.
We keep incrementally building the migration guide for LoopBack 3 users upgrading to LoopBack 4. In November, we added content for migrating model relations. We explained how to convert a relation defined in LoopBack 3 model JSON files into corresponding LoopBack 4 artifacts.
In loopback4-example-shopping
, the /users/{userId}/orders
endpoints are now secured by an authorization system. When a request comes in, the authentication module resolves the user profile and passes it to the authorization module. Then an interceptor retrieves the metadata from the decorated endpoint, and invokes registered authorizers to determine whether the user can perform the operation. We also have a documentation PR in progress that explains the usage of authorization module.
In LoopBack 3.x, we store users' passwords together with user data in the same model (table), which opens a lot of security vulnerabilities to deal with. For example, when returning user data in HTTP response, the password property must be filtered out and when searching for users, the password must be excluded from the query.
Our example Shopping App used to store user credentials together with other profile information too. In November, we refactored the domain model of our example app and extracted the password property into a new model UserCredentials
. Beside the immediate benefits in increased security, this new domain model makes it easier to implement additional features in the future. For example: 2-factor authentication, and the password validation rule forbiding repeated use of the same password.
In PR#4193 we've improved the context/binding with an inspect()
method for metadata dumping. ctx.inspect()
can now be used to print out the context hierarchy in JSON. This is useful for troubleshooting and rendering in UI. An example snippet of calling the new function:
const ctx = new Context();
console.log(ctx.inspect());
When creating a binding, you can configure the scope
as SINGLETON
or TRANSIENT
. To know more about how to make the right choice based on requests, see the new documentation in Choosing the right scope.
A new shortcut decorator @service
was introduced to inject an instance of a given service from a binding that matches the service interface. You can inject a service as follows:
class MyController {
constructor(@service(MyService) public myService: MyService) {}
}
More details of its usage and explanation could be found in Service Decorator
PR#4121 allows a class or provider to receive its own binding information as follow:
export class HelloController {
// If the `bindingKey` is not specified,
// the current binding from the resolution session is injected.
@inject.binding() private myBinding: Binding<string>;
@get('/hello')
async greet() {
return `Hello from ${this.myBinding.key}`;
}
}
In PR#4145 we improved states and introduced graceful shutdown for LoopBack applications.
Now an application's states are classified as stable or in process. Operations can only be called at a stable state. Calling a different operation in an in-process state will throw an error. See Application states for details.
The shutdown of application is now controllable by specifying an array of signals in the configuration. For example:
const app = new Application({
shutdown: {
signals: ['SIGINT'],
},
});
// Schedule some work such as a timer or database connection
await app.start();
When the application is running inside a terminal, it can respond to Ctrl+C
, which sends SIGINT
to the process. The application calls stop
first before it exits with the captured signal. This gives you a better control when the LoopBack 4 application is running inside a managed container, such as a Kubernetes Pod. See graceful-shutdown for more details.
We've finished the Partitioned Database Epic in loopback-connector-cloudant
by supporting partitioned index, find, and property definition.
To create partitioned indexes as the secondary optimization for Cloudant query, you can add index entry in your model configuration with {partitioned: true}
like:
Product = db.define('Product', {
name: {type: String},
}, {
forceId: false,
indexes: {
// create a partitioned index for frequently queried
// fields like `name`
'product_name_index': {
partitioned: true,
keys: {
name: 1
},
},
}
});
Learn more about its signature and usage in Adding Partitioned Index
When the partition key is discovered in the query's options or filter, Model.find()
will invoke the underlying partitioned find to optimize the query. Here are two examples:
Specifying the partition key in the options
: Product.find({where: {name: 'food'}}, {partitionKey: 'toronto'});
Or defining a model property as the partition key field. So that the find method could infer the partition key from the query filter:
// Define `city` as the partition key field in model `Product`
Product = db.define('Product', {
id: {type: String, id: true},
name: String,
// partition key field
city: {type: String, isPartitionKey: true},
});
// `Product.find()` will infer the partition key `toronto`
// from the filter
Product.find({
where: {
city: 'toronto',
name: 'food'
}
});
You can learn more about the partitioned query in Performing Partitioned Find
We upgraded the version of TypeScript to 3.7.2. An application created by the new CLI module ^@loopback/cli@1.26.0
is able to use the latest TypeScript features such as optional chaining and nullish coalescing. A list of new features can be found in TypeScript's release blog.
Now, in the new generated application, the json configuration files are renamed from *.datasource.json
to *.datasource.config.json
to avoid generating declarations. Explanations see TypeScript issue#34761.
lb4 discover
should generate the correct type for property definition. Fixed by PR#4143.
@param.path.<primitive_type>
generated with lb4 relation
considers Wrapper datatypes. Also fixed by PR#4143.
We've added a series of tutorials to illustrate how LoopBack can be used as an enabler to build large-scale Node.js applications. If you want to have a deeper understanding of LoopBack and/or to build an application with great flexibility and extensibility, don't miss this tutorial series!
In loopback-component-explorer
, we added a note in the README file to explain why the module is not affected by the security vulnerabilities in swagger-ui
.
We've added "Boot" and "Advanced Topics" to the core tutorial in Advanced Recipes and Discover and load artifacts by convention.
We've updated the steps of creating model relations to use lb4 relation
command in the TodoList tutorial.
We added the OpenAPI and gRPC connectors to be a part of our available connectors in PR#558 and PR#906. Now, when a user calls lb4 datasource
, they will have OpenAPI and gRPC as options for the connector.
The lb4 update
command runs against a LoopBack 4 project and checks dependencies against the installed @loopback/cli
. Optionally, it updates the dependencies in package.json
. Details can be found on page Update generator.
In spike story#3770 we came up with a plan to support querying with nested filter in the API Explorer by re-designing the @param.query.object()
decorator. The follow-up implementation story is tracked in #2208.
We fixed a bug in loopback-datasource-juggler
where applyDefaultOnWrites
was not being applied in nested objects and arrays. You can find the details in PR#1797.
Here are some of the highlighted contributions from the community:
For the model definition created by running lb4 openapi
, we fixed the JavaScript type mapping of date
from Date
to string
. Details see PR#142.
A flag useDefaultIdType
was introduced in loopback-datasource-juggler
to preserve the user provided id(primary key) property against the one generated by connectors. Details see PR#1783.
We now reject the the promise properly for create
and replaceById
when model initialization has errors. Details see PR#1790.
If you're interested in what we're working on next, you can check out the December Milestone.
LoopBack's success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Here's how you can join us and help the project:
When we build APIs and microservices nowadays, we choose a cloud as the target for deployment. Cloud has long gone beyond being just hosting providers. Infrastructures such as Docker and Kubernetes have completely changed the paradigm of how applications work and operate. To unleash the full power of cloud, there are a few important perspectives that require efforts to make your application cloud native. At LoopBack, we kicked off the journey to provide integration and guidance aligned with CNCF to make your API and microservice applications cloud native throughout the life cycle. This blog summarizes what we have explored and achieved so far to illustrate how you can go cloud native with LoopBack 4.
We have looked into the following areas to understand how to make a LoopBack application cloud native and assess what it takes to improve LoopBack framework to be a good citizen of the cloud ecosystem.
The shopping example started as a monolithic application in early versions. It has been refactored and improved over time to make the application modular.
We did some experiments to decompose loopback4-example-shopping
into microservices, package them as Docker containers, and deploy them into a Kubernetes cluster. The whole story can be read here.
Key takeaways:
Developers are often disconnected from the cloud environment during the development phase, with code only verified on a developer's local machine. Bad surprises may rise late in the cycle when deployment and tests happen in the cloud against that code.
Kabanero is created to address such concerns. The following is quoted from Kabanero's web site:
Kabanero is an open source project focused on bringing together foundational open source technologies into a modern microservices-based framework. Developing apps for container platforms requires harmony between developers, architects, and operations. Today’s developers need to be efficient at much more than writing code. Architects and operations get overloaded with choices, standards, and compliance. Kabanero speeds development of applications built for Kubernetes while meeting the technology standards and policies your company defines. Design, develop, deploy, and manage with speed and control!
To learn how Kabanero works, head here.
To bring the LoopBack offering to the Kabanero experience, we have introduced an Appsody Stack for LoopBack 4.
The Node.js LoopBack stack extends the Node.js stack and provides a powerful solution to build open APIs and microservices in TypeScript with LoopBack, an open source Node.js API framework. It is based on LoopBack 4.
Go here for more details.
Observability is critical to the success of cloud native microservices. To make LoopBack a good citizen of Kubernetes based cluster, we have been rolling out extensions to integrate with health, metrics, tracing, and logging capabilities, based on projects at CNCF.
Released experimental features
New features proposed
LoopBack 4 applications hosted by Kubernetes Pods can be requested to shutdown per provisioning needs by the cluster. The life-cycle and hand-share are described here.
With the investigation and experiment, we were able to deploy loopback4-example-shopping
as an application with cloud native microservices to a Kubernetes cluster hosted by IBM Cloud. The LoopBack stack for Kabanero/Appsody is released. There are also pull requests under reviews to close gaps and add new facilities. We're very excited that LoopBack 4 is going cloud native and we're even more interested in seeing LoopBack applications going cloud native with us. Please join us on the journey.
LoopBack's success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Please join us and help the project by:
This is the final episode of this series. We've used LoopBack 4 to build an online web text-based adventure game. We've built the foundation with LoopBack, and I am going to summarize what we have achieved so far, and how can you apply all of this to your own project.
In episode 1, we created a simple APIs. You can do the same to create a start point for your own project, for example, a student registration system which has a student
model with properties like studentId
, name
, major
, and course
. Then we connected our project to MongoDB. You have the freedom to choose any database you want. LB4 supports most databases very well.
In episode 2, we used a third-party library to generate UUID. You can easily use any external library in you LoopBack 4 project. We also built relations between character
, weapon
, aromr
, and skill
. In a real world application, most of entities have relationships between each other. You can use LoopBack 4 to easily manage that in your project.
In episode 3, we covered how to customize APIs to achieve the function to manage users data. You can always implement your own amazing idea in your LoopBack 4 project.
In episode 4, we combined self-defined authorization strategies and services with @loopback/authentication
and how to apply it to your API. You can always design your own strategies and services based on your project needs.
In episode 5, we deployed our project with Docker and Kubernetes on IBM Cloud. Once you create a Docker image, you can run it almost everywhere. You can also push your own project image to other cloud like AWS, Azure, and Google Cloud.
In episode 6, we created simple login, signup and home pages with React. We also learned how to connect front-end and back-end. React is the most popular front-end framework today. You can easily use it to create your own front-end UI.
Congratulations! You have built your own web application with LoopBack!
What we built in this series doesn't matter. It doesn't have to be an online game! You can use these concepts for online shopping or food delivery APIs. What is important is improving your design thinking, what methodology and tools we were using, and adding to the ways you think as a developer. Now, you've established stronger skills to build!
I hope you enjoyed this series. Thank you so much for all of the following and support. The series may be ending, but your journey with LoopBack is just starting.
Happy coding!
]]>CASCONxEVOKE is one of Canada’s largest combined academic, research and developer conferences, welcoming 1,500+ attendees and 150+ speakers. This year, the LoopBack team attended and kept busy collaborating with other attendees at the LoopBack booth and delivering a workshop at the conference between November 4th and 6th in Markham Ontario
At our booth we had a great opportunity to show how LoopBack can make API creation fast and easy. It looks like the use case we've shown in our poster is a common use case for our attendees to build APIs!
We also got a few questions about exposing GraphQL in LoopBack. If you're interested in it too, see our tutorial which uses the OpenAPI-to-GraphQL module.
On Day 2, we held a workshop about writing scalable and extensible Node.js applications using LoopBack 4. We presented the challenges we faced for LoopBack as a large scale Node.js framework and showed how we are addressing those challenges in LoopBack 4. While we introduced the concepts that make scalability and extensibility possible (such as Dependency Injection, extension/extension-point framework and Inversion of Control), the attendees also had a chance to build an extensible and scalable Node.js application step-by-step using those features.
We won't be able to relive the workshop, but you can check out our workshop hands-on exercise instructions and presentation slides.
Throughout the conference, the IBM Developer booth was showcasing different developer-focused technologies, such as Appsody and LoopBack. We were delighted to be there on Day 3 to reach out more existing and potential users!
Some attendees asked about cloud deployment story for LoopBack. We have been focusing on a vendor agnostic approach. For instance, we recently completed a PoC and added a tutorial on how to deploy a LoopBack application as microservices using Kubernetes. While we recommend to deploy your applications to IBM Cloud, you can also deploy to another vendor of your choice.
LoopBack's success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Please join us and help the project by:
As the cold autumn winds and frost nipped at our heels, the LoopBack team kept warm with generous portions of hot tea and coffee and accomplished their planned October milestone goals.
We focused on the following areas:
See the October milestone for an overview of what we have worked on, and read on for more details.
Also, we were honored this month when API World awarded LoopBack with the 2019 Best of API Middleware Award
. Raymond Feng was there to accept the award on behalf of the team. Yay, team!
We've been working on inclusion resolvers for relations for the past several months. Besides the basic functionality, we also added a prompt for activating the inclusion resolver for the lb4 relation
command in PR #3856. This allows users to easily set up the inclusion resolver through the CLI just like all others artifacts.
The lb4 relation
command now prompts to confirm if an inclusion resolver should be registered for the given relation.
Also, we posted a blog to illustrate the idea and usage of the inclusion resolver. We have a full example from setting up the models and relations through CLI, to querying data with the inclusion resolver. Read the blog to try out the feature!
We've gotten feedback from the community since this feature was published. As a result, we've improved the documentation. Diagrams were added to each relation to make the concept more intuitive. We also added URLs as examples to query related models in case users want to process data at the controller level instead of the repository level. See PR #4007 for more details.
Both Cloudant and CouchDB support partitioned databases which make querying less expensive - computationally in CouchDB, monetarily on Cloudant (on IBM Cloud
).
A spike was performed to investigate the work required to support the partitioned database in Cloudant, and follow-up tasks were created. Epic #219 - Add support for partitioned database was created to track the implementation stories.
The feature will be supported in 3 stages. In the first stage, we will update the driver and enable creating the partitioned index; which are the pre-requisites for the partitioned search. The second stage includes supporting the query with partition key from the options data or the payload data; the latter one requires a model property defined as the partition key field. Stage 3 contains more improvements for the document creation in the partitioned database by supporting a composed ID.
To start Epic #219, we updated the cloudant driver and the docker image to the latest ones which support the partition feature.
A spike was performed on migrating from LoopBack 3 to LoopBack 4. As a result, we created the outline of a migration guide.
The shopping cart application has undergone a few improvements.
We have added an out-of-the-box capability to set a JWT token via an Authorize button/dialog
in the API Explorer. When interacting with the secured endpoint, the token is automatically sent in the Authorization
header of the request. See PR #301 and PR #3876 for details. To find out how to enable this capability in your application, please see Specifying the Security Settings in the OpenAPI Specification.
The shopping cart application has been decomposed into multiple microservices, each of which is packaged as a docker image. It's possible to communicate between microservices using REST and/or gRPC. A helm chart can now be used to deploy these microservices to a Kubernetes cluster on Minikube or IBM Cloud. See Deploy the Shopping Application as Cloud-native Microservices using Kubernetes for more details.
The shopping
service can now connect to the recommender
service over gRPC as well as REST. See PR #333 for details.
Extra logic was added to *.datasources.ts
files to accept configuration from kubernetes environment variables. See PR #338 for details.
The LoopBack 4 team has prepared material for a booth and workshop at CASCON x Evoke 2019 in Toronto, Canada. We hosted a booth named REST APIs with LoopBack 4 and OpenAPI 3 on November 4th and 6th and held a workshop named Write scalable and extensible Node.js applications using LoopBack 4 on November 5th. Watch for an upcoming blog post with an overview of the event!
Previously, we ran repository tests against the memory, MySQL, and MongoDB databases. This month, we added PostgreSQL to the databases these tests are run against. You can see PR #3853 for details or take a look at the newly added repository-postgresql
package.
Some progress was made on the EPIC #2036 - From model definition to REST API with no custom repository/controller classes. A controller can now be generated based on a model name. See PR #3842 for details. Also, a repository can now be generated based on a model name. See PR #3867 for details.
We're always seeking to improve our documentation. We've added a new section Inside a Loopback Application to help LoopBack 4 application developers to establish a high level understanding of how LoopBack 4 is related to their application requirements.
After triaging some issues from the community, we realized that the documentation for customizing key names needs to be enhanced. We added explanations and examples to illustrate the default value of relations, how to customize key names, and how to use different names for models and database columns. You can find more details in HasMany - Relation Metadata, HasOne - Relation Metadata, and Defining a belongsTo Relation.
Fixed Issue #4252 - Fix CI builds (Karma + PhantomJS) by reworking browser tests to run in Headless Chrome instead of PhantomJS, because the latter is no longer maintained. See PR #4262 for details.
Fixed Issue #3717 - PersistedModel's updateAll method crashes the server when invoked via the remote connector by properly handling anonymous object types when PersistedModel.updateAll
is called. See PR #472 for details.
Fixed Issue #3878 - lifeCycleObserver is not working with express composition. See PR #3879 and PR #3891 for details.
Fixed Issue #3706 - Unable to POST on endpoint with recursive model. See PR #3897 for details.
Fixed Issue #3296 - Model discovery not working with Oracle connector & CDB instance by only calling back once the connection is released. See PR #193 for details.
As a LoopBack user, do you want your company highlighted on our web site? If your answer is yes, see the details in this GitHub issue.
If you're interested in what we're working on next, you can check out the November Milestone.
LoopBack's success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Here's how you can join us and help the project:
LoopBack 4 now offers a new feature: inclusion of related models! This addition not only simplifies querying data in LoopBack 4, but since we have similar features in LoopBack 3 it also closes one feature gap between LoopBack 3 as well. The idea is to use the inclusion resolver
, which is a function that helps to query data over different relations, to achieve such simplifications for us.
Here is a simple use case of inclusion: a customer has many orders.
If we'd like to get a customer's instance with all their orders instances, we can query on Customer
with filter {include: [{relation: 'orders']}
. The inclusion resolvers are similar to GraphQL resolvers -- it will find the target instances of Customer
first and pass its result to the inclusion resolver of orders
. The query result will contain the return value of orders
nested under corresponding Customer
instead of connecting to database twice. Read on for detailed examples and explanations!
LoopBack 4 creates a different inclusion resolver for each relation type. Each relation has its own inclusion resolver inclusionResolver
. And each repository has a built-in property inclusionResolvers
as a registry for its inclusionResolver
s.
To enable querying related models for a certain relation, the corresponding inclusionResolver
of that relation has to be registered to the inclusionResolvers
. I promise the set up is not as complicated as what you just read through.
Let me show you the steps to enable this feature in few steps!
Upgrade your global installation of LoopBack 4 command line interface (CLI) to get the new feature.
$ npm install -g "@loopback/cli"
You can set up models and datasource by the CLI lb4 model
and lb4 datasouce
.
I use MySQL
as my database in this case. And here are my models Customer
and Order
:
customer.model.ts
:
// imports
@model()
export class Customer extends Entity {
@property({
id: true,
generated: true
})
id: number;
@property({
type: "string"
})
name: string;
// constructor
}
order.model.ts
:
@model()
export class Order extends Entity {
@property({
id: true,
generated: true
})
id: number;
@property({
type: "string",
required: true
})
description: string;
}
//constructor
We are setting up two relations in this example:
hasMany
: a Customer
has many Order
s. Named this relation as orders
.belongsTo
: an Order
has a Customer
. Named this relation as customer
. The foreign key is customerId
.You can either modify your model and repository files or use CLI lb4 relation
to set up relations and enable the inclusionResolver
in each relation. Here's how I set the belongsTo
relation through the CLI:
$ lb4 relation
? Please select the relation type: belongsTo
? Please select source model: Order
? Please select target model: Customer
? Source property name for the relation getter: customerId
? Allow Order queries to include data from related Customer instances? Yes
create src/controllers/order-customer.controller.ts
Relation BelongsTo was created in src/
This prompt registers the inclusionResolver
for this belongsTo
relation for you.
? Allow Order queries to include data from related Customer instances? (Y/n)
It defaults to Yes
. Make sure to choose 'yes' if you'd like to use inclusion and your model is traversable.
Here is the code snippet for models after setting up two relations and enabling both inclusion resolvers:
customer.model.ts
:
// imports
@model()
export class Customer extends Entity {
// id, name properties
@hasMany(() => Order)
orders?: Order[];
// constructor
}
order.model.ts
:
// imports
@model()
export class Order extends Entity {
// id, desc properties
@belongsTo(() => Customer)
customerId: Customer;
}
//constructor
And you'll see the inclusion resolvers are enabled in the repository classes:
customer.repository.ts
:
//imports
export class CustomerRepository extends DefaultCrudRepository {
public readonly orders: HasManyRepositoryFactory<
Order,
typeof Customer.prototype.id
>;
constructor(
dataSource: DbDataSource,
orderRepositoryGetter: Getter<OrderRepository>
) {
super(Customer, dataSource);
this.orders = this.createHasManyRepositoryFactoryFor(
"orders",
orderRepositoryGetter
);
// this line registers inclusion resolver, allows us to query related models
this.registerInclusionResolver("orders", this.orders.inclusionResolver);
}
}
order.repository.ts
:
export class OrderRepository extends DefaultCrudRepository {
public readonly customer: BelongsToAccessor<
Customer,
typeof Order.prototype.id
>;
constructor(
dataSource: juggler.DataSource,
customerRepositoryGetter: Getter<CustomerRepository>
) {
super(Order, dataSource);
this.customer = this.createBelongsToAccessorFor(
"customer",
customerRepositoryGetter
);
// this line registers inclusion resolver, allows us to query related models
this.registerInclusionResolver("customer", this.customer.inclusionResolver);
}
}
Notice: I use default values in this example. We also recommend to follow the naming convention. If you'd like to custom property names or relation names, check our site Relations for more details.
At this point, you're able to query related models by specifying the relation name in the inclusion field. Let's create instances for Customer
and Order
.
Customer: [
{id: 1, name: `Thor`},
{id: 2, name: `Captain`},
],
Order: [
{id: 1, desc: `Rocket Raccoon`, customerId: 1},
{id: 2, desc: `Shield`, customerId: 2},
{id: 3, desc: `Mjolnir`, customerId: 1},
]
You can either query data via controllers or do it in the repository level.
export class CustomerController {
// constructor
@get('/customers', {
...
}
export class OrderController {
// constructor
@get('/orders', {
...
}
For hasMany relation orders
, these queries return all customers with their Order
s:
Use controllers (or use the API Explorer http://localhost:3000/explorer/
):
GET http://localhost:3000/customers?filter[include][][relation]=orders
This is the same as you process data in the repository level:
await customerRepository.find({ include: [{ relation: "orders" }] });
Result:
[
{
id: 1,
name: 'Thor',
orders: [
{id: 1, desc: 'Mjolnir', customerId: 1},
{id: 3, desc: 'Rocket Raccoon', customerId: 1},
],
},
{
id: 2,
name: 'Captain',
orders: [{id: 2, desc: 'Shield', customerId: 2}],
},
];
Here is a diagram to make this more intuitive:
For belongsTo relation customer
, these queries return the Order
that has id = 1
and includes the Customer
it belongs to.
Use controllers (or use the API Explorer http://localhost:3000/explorer/
):
GET http://localhost:3000/orders/1?filter[include][][relation]=customer
This is the same as you process data in the repository level:
await orderRepository.findById(1, {include: [{relation: 'customer'}]};)
Result:
[
{
id: 1,
desc: 'Rocket Raccoon',
customerId: 1,
customer: {id: 1, name: 'Thor'},
},
]
Besides the example I've shown above, our TodoList Tutorial example also uses inclusion. Check on the site for more detailed steps.
Hope this new feature is helpful for you!
Though we've finished the implementation of this new feature and test it against SQL and NoSQL databases, there are some limitations:
orders
of a Customer
, it cannot filter a certain Order
s that you want to include in orders
. Related GH issue: Include related models with a custom scopeinq
splitting.ObjectId
of MongoDB. Related GH issue: Spike: robust handling of ObjectID type for MongoDBWe have some discussions on these issues. Please check out the Post MVP Enhancement story if you're interested. We'd love to hear your input and feel free to contribute.
Thanks for choosing LoopBack!
LoopBack's success depends on you! We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Here's how you can join us and help the project:
For the past few months, the LoopBack team has been busy improving the framework. Aside from implementation, we also did some investigation to plan out road map for the incoming new features. Here are our main focuses in last quarter:
@loopback/authentication@3.x
version.inclusionResolver
.lb4 import-lb3-models
.We have a monthly blog reviewing what we've done in each milestone. To stay tuned, don't forget to follow us on Twitter @StrongLoop.
Let's take a closer look at each of the epic.
We have recently released @loopback/authentication
3.0. We refactored some common types/interfaces in both authentication and authorization packages and moved them to the new @loopback/security
package. This helps combine the operations over these two packages. We also improved the @authenticate
decorator so that users can apply the default authentication metadata without adding the decorator @authenticate
to every route. Also, methods that don't require authentication can be skipped by adding @authenticate.skip
.
Apart from enhancing codebase, we also updated the related documentation and examples. Developers can easily follow tutorials and examples to try out setting up their own authentication systems.
Moreover, we did some researches into potential solutions for the new features. In spike #267 we were exploring a way to enable the authorization header setting from the API Explorer. And in spike #3771, we tried to find a solution to make UserProfile
interface more flexible so that users have more controls over authentication. Check the links for discussions and proposal details.
We released an experimental version of @loopback/authorization
. The authorization system now allows developers to decorate their endpoints with @authorize()
. By plugging in their own authorizers, it is able to determine whether a user has access to the resource.
Besides the basic functionalities, we also made @authorize()
more flexible: users now can define their own voters/authorizers, apply @authorize
at the class level, and use the new @authorize.skip
annotation. All these improvements allow developers to have more control to shape the authorization system.
We have made good progress in this epic this quarter. We completed and released the implementation of inclusionResolver
of relations. It allows LoopBack 4 users to query data over relations more easily. The todo-list
tutorial is also updated with the usage of inclusionResolver
. Check LB4 site Relations and the todo-list
tutorial to get started. We will make this feature easier to use by adding it to CLI command. Stay tuned for more details!
In LoopBack 3, it was very easy to get a fully-featured CRUD REST API with very little code. We would like to provide the same simplicity to you that you can create REST APIs from models directly without the need of creating Repository or Controller classes. As we found out and discussed in spike #3617, we will introduce a new package @loopback/model-api-builder
, a new booter ModelApiBooter
, and a plugged-in model-api-builder that builds CRUD REST APIs. The proposed design will enable users to create REST APIs without customizing repository/controller classes. Check Epic #2036 for details of related stories.
We accomplished the implementation for the CLI command to import model definitions from LoopBack 3 applications. If you have existing LoopBack 3 applications, it's a good time to type in command lb4 import-lb3-models
to migrate them to LoopBack 4! See PR #3688 for implementation details, Importing models from LoopBack 3 projects documentations, and also the blog post.
In August, we got the news from APIWorld that LoopBack has won the "Best In API Middleware" award. We have shared this news in this blog post. Our architect and co-creator of LoopBack, Raymond Feng, will be attending the awards ceremony on October 8 at APIWorld. If you happen to be at the conference, don't miss out.
To moving towards the cloud native direction, LoopBack 4 has been added to be one of the Appsody application stacks. It is also being offered as part of the IBM Cloud Pak for Applications. With the health check extension being added to LoopBack 4, we are planning to add more capabilities to provide the non-functional requirements for the cloud native deployment. Stay tuned!
For the next 3 months, we'd like to focus on the following:
Check Q4 roadmap for more details.
There are too many features added and bug fixes that cannot be captured here. Check out our previously published monthly milestone blog posts in Q2 for more details:
LoopBack's future success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Please join us and help the project by:
It has been almost a year since LoopBack 4.0 GA was announced. Since then, we have been working hard on closing the feature gap between the new and the old versions and looking for ways to simplify migration of projects built on LoopBack 3.
In June, we announced a new feature that allows LoopBack 3 applications to be mounted in LoopBack 4 projects, allowing developers to start writing new features using LoopBack 4 while keeping existing APIs powered by LoopBack 3 (Read more about it in this blog post).
Today, we are happy to announce a preview version of a tool automating migration of models from LoopBack 3 to LoopBack 4:
lb4 import-lb3-models
Let me show you the new command in practice, using our lb3-application
example, which is based on Getting started with LoopBack 3.
First of all, upgrade your global installation of LoopBack 4 CLI to get the new feature!
$ npm install -g "@loopback/cli"
Now we can run the following command to start the migration process:
$ lb4 import-lb3-models lb3app/server/server.js
A side note: in our example project, the LoopBack 3 application is a part of the root LoopBack 4 project. Therefore it does not have its own package.json
file and LoopBack 3 dependencies are included in the top-level package.json
file along LoopBack 4 dependencies. The directory of the LoopBack 3 application cannot be loaded directly via require
as a result, and we have to provide a full path to the server file to the CLI tool. On the other hand, if you are importing from a standalone LoopBack 3 project which has main
entry in package.json
configured to point to server/server.js
(as is the case with projects scaffolded by our LoopBack 3 CLI tool), then it's enough to use the path to your LoopBack 3 project directory as the argument, e.g. lb3app
).
The generator will greet you with a warning about the experimental status and then load the LoopBack 3 application at the provided path. Once the application is loaded, the generator shows a list of models available for import.
? Select models to import: (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◯ Application
◯ AccessToken
◯ User
◯ RoleMapping
◯ Role
◯ ACL
◯ Scope
(Move up and down to reveal more choices)
The initial version includes built-in LoopBack 3 models too, because they don't have any direct counterparts in LoopBack 4. We would like to investigate different options for importing models based on LoopBack 3 built-in models. Depending on the findings, the behavior of this prompt may change in the future.
Using arrows and the spacebar, we select the CoffeeShop
model to import and confirm the selection.
? Select models to import:
◯ Role
◯ ACL
◯ Scope
❯◉ CoffeeShop
◯ Application
◯ AccessToken
◯ User
(Move up and down to reveal more choices)
Now the generator migrates the model definition to the LoopBack 4 style, creates a TypeScript model file, and also updates the relevant index file.
? Select models to import: CoffeeShop
Model CoffeeShop will be created in src/models/coffee-shop.model.ts
Ignoring the following unsupported settings: acls
create src/models/coffee-shop.model.ts
update src/models/index.ts
The definition of CoffeeShop
model includes access-control configuration, which is not supported by LoopBack 4. The tool will warn about other unsupported fields besides acls
, for example relations
and methods
.
The initial release has a few more limitations beyond missing support for acls
. Please refer to our documentation at Importing models from LoopBack 3 projects.
We are releasing this early preview version to get feedback from you, our users! Please give the new command a try and let us know which parts of the migration experience we should improve next. Start by checking the known limitations described in the documentation and up-vote the linked GitHub issues. If there is no GitHub issue describing your feature yet then please open a new one.
Besides importing model definitions, we are also working on a declarative way of exposing models via REST APIs. This will allow LoopBack 4 applications to be written in a style that's closer to LoopBack 3, where REST API are built from a model definition file (e.g. common/models/product.json
) and model configuration file (server/model-config.json
). Once this feature is implemented, it will be possible to migrate both model definition and REST API from LoopBack 3. You can track our progress in GitHub issues loopback-next#2036 and loopback-next#3822.
LoopBack's success depends on you! We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Here's how you can join us and help the project:
All the leaves are red and the flowers are fading away. In this season of fruitfulness, the LoopBack team accomplished their planned September milestone goals. Besides delivering code-related contributions, we also addressed the growing number of reported issues from the growing number of users from the GitHub community. I believe that with the effort from our team and the contribution from the community, we are crafting LoopBack to a better framework.
Here are our main focuses from September:
See the September milestone for an overview of what we have worked on, and read on for more details.
@authenticate
and @authorize
DecoratorsIn PR #3691, we implemented the default metadata for authentication. It allows us to have a default authentication enforcement for methods. We also improved the @authenticate
decorator to configure a default authentication for all methods within a class. Now we can apply our default authentication metadata without adding the decorator @authenticate
to every route.
Moreover, methods that don't require authentication can be skipped by adding @authenticate.skip
. Similarly, authorization can be skipped with @authorize.skip
annotation. Check PR #3762 for more details.
In spike #267 we tried enabling the authorization header setting from the API Explorer. Without this feature, users will have to test their endpoints outside the API Explorer to specify the header fields.
The swagger-ui
module has its built-in "Authorize" component. It shows up in the API Explorer automatically when components.securities
is added in the application's OpenAPI specification. In the spike PR, a series of screenshots demos how to interact with the "Authorize" dialog to set the authentication headers. You can either specify global security policies to be applied to all the APIs, or configure the policy per endpoint.
When merging the security schemas into the OpenAPI specification, we also realized the importance to make contributing partial OpenAPI specifications from extensions more flexible. As the next steps, we will document and explain the steps of enabling the "Authorize" component in the API Explorer, and also improve inferring security schemas from the authentication strategies.
UserProfile
Interface in @loopback/authentication
Interface UserProfile
describes a minimum set of properties that represents an identity of a user. It is also a contract shared between the authentication and authorization modules. To make up the difference between a custom User
model and UserProfile
, we introduced a UserService
interface with a converter function as a choice for users who want to organize the utilities for the User
model into a service class.
In this spike, we worked on a more general solution that unifies the behavior across all modules. We started the spike with the @loopback/authentication-passport
because compared with custom authentication strategies, users have less control over the returned user when applying the passport adapter. The solution is to define an interface UserProfileFactory
which takes in an instance of the custom user and returns a user profile. A corresponding key will also be created in @loopback/authentication
for the sake of injection.
In the meantime, we also discovered that some user profile related refactor in @loopback/authorization
is needed. You can find more details in the PoC PR
Two follow-up stories were created: Add user profile factory and Refactor the authorization component
In LoopBack 3, it was very easy to get a fully-featured CRUD REST API with very little code: a model definition describing model properties + a model configuration specifying which datasource to use.
In September, we did some research into how to provide the same simplicity to LB4 users too and outlined the following implementation design:
A new package @loopback/model-api-builder
will define the contract for plugins (extensions) contributing repository & controller builders.
A new booter ModelApiBooter
will load all model-config files from /model-endpoints/{model-name}.{api-flavour}-config.js
, find model API builder using Extension/ExtensionPoint pattern and delegate the remaining work to the plugin.
An official model-api-builder plugin that will build CRUD REST APIs using DefaultCrudRepository
implementation. The plugin will be implemented inside the recently introduced package @loopback/rest-crud
.
The proposed design will enable the following opportunities to extend and customize the default behavior of API endpoints:
App developers will be able to create & bind a custom repository class, this will allow them, for example, to implement functionality similar to LB3 Operation Hooks.
App developers will be able to implement their own api-builder plugins and replace the repository & controller builders provided by LB4 with their own logic.
The model configuration schema will be extensible; individual plugins will be able to define additional model-endpoints options to further tweak the behavior of API endpoints.
You can find more details in the spike PR #3617 and track the implementation progress in Epic #2036.
To further simplify the migration from LoopBack 3 to LoopBack 4, we implemented a new CLI command to import model definitions from LoopBack 3 applications. See PR #3688, Importing models from LoopBack 3 projects in our documentations and stay tuned for an upcoming blog post announcement!
Last month, we introduced the concept of the inclusion resolver
in relations, which helps to query data through an include
filter. An inclusion resolver is a function that can fetch target models for a given list of source model instances.
Here is an example of the HasMany
relation: querying customers' orders. A Customer
has many Order
s:
customerRepository.find({ include: [{ relation: "orders" }] });
will return all customer instances alone with their related orders:
[
{
customerName: "Thor",
id: 1,
orders: [
{ desc: "Mjolnir", customerId: 1 },
{ desc: "Rocket Raccoon", customerId: 1 }
]
},
{
customerName: "Captain",
id: 2,
orders: [{ desc: "Shield", customerId: 2 }]
}
];
We accomplished finishing the implementation of inclusion resolver
for HasMany
, BelongsTo
, and HasOne
relations. The inclusionResolver
property is now available for these three relations.
For more details on how to use the resolvers, please check the LoopBack 4 Relation page or try out the todo-list
example (by using lb4 example todo-list
).
For implementation details, please check pull requests of inclusion resolver tasks and its helpers:
HasMany
inclusion resolver implementation PR #3595BelongsTo
inclusion resolver implementation PR #3721HasOne
inclusion resolver implementation PR #3764todo-list
example to use inclusion resolver PR #3450keyFrom
to resolved relation metadata PR #3726PR #3674 and PR #3722 allow us to define recursive models. For example:
@model()
class ReportState extends Model {
@property.array(ReportState, {})
states: ReportState[];
@property({
type: "string"
})
benchmarkId?: string;
constructor(data?: Partial<ReportState>) {
super(data);
}
}
where the JsonSchema of the ReportState
model is
{
states: {
type: 'array',
items: {$ref: '#/definitions/ReportState'},
},
benchmarkId: {type: 'string'},
}
The CI failures in the Oracle connector have been fixed. In a related note, a new issue has been opened to make the local tests (npm test
) pass.
Previously, npm run migrate
would exclude LoopBack 3 models mounted in a LoopBack 4 app. Now it successfully migrates LoopBack 3 models as well. Check PR #3779 for more details.
Through the work in progress PR #268, by decomposing shopping example into multiple microservices and packaging them as docker images, we will be able to deploy a composite application. In addition, this feature would also show the posibility of communicating between microservices using REST and/or gRPC in such LB4 applications.
Prometheus
In another work in progress PR #3339, we are working on an experimental functionality. The new @loopback/extension-metrics
package would allow users to report metrics of Node.js, LoopBack framework, and their application to Prometheus
. This feature will be shown as an example in the new package @loopback/example-metrics-prometheus
.
We have many community users contributing to LoopBack. In September, 14% of the merged PRs was coming from community contribution! From refactoring functionality to enhancing documentation, we really appreciate all these contributions. Here are some highlights of this month's community contributions:
rest-explorer
to Use a Relative URL for the OpenAPI SpecificationCommunity user @mgabeler-lee-6rs implemented a new feature that allows API Explorer to self-host the OpenAPI spec document and thus use a relative path in swagger-ui
configuration. Before this change, the LoopBack configured UI component with an absolute path that did not work well when the application was running behind a reverse proxy link nginx
. The API Explorer now works more reliably. Check PR #3133 for more implementation details and discussions over the feature.
In PR #3504, community user @ericzon changed the formatting of the OpenAPI Schema, which allows it to now pass Azure API Manager validation. We can now deploy an API as "App Service" and connect it with the API Manager as OpenAPI. It enables LB4 to work out-of-the-box on Microsoft Azure.
@derdeka is one of our most active users and contributors. @derdeka shared their ideas and suggestions in many pull requests with us, especially in the Authentication
area. Here are some of their contributions:
AuthenticationStrategyProvider
class to be extended.If you would like to contribute to LoopBack, please check the Call to Action section below!
As a LoopBack user, do you want your company highlighted on our web site? If your answer is yes, see the details in this GitHub issue.
If you're interested in what we're working on next, you can check out the October milestone.
LoopBack's success depends on you! We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Here's how you can join us and help the project:
The last month of summer started with exiting news that LoopBack won the "Best in API Middleware award"🎉, which is a great recognition of our team's achievement and effort. With those efforts in mind, here's the highlights of what we worked on in August:
You can read more to learn the new features in details.
To deliver Inclusion of related models epic, we started to implement tasks related to the inclusion resolver.
We introduced the type InclusionResolver
which you can use to fetch related models in PR #3517.
We implemented findByForeignKeys
and includeRelatedModels
functions, which help build resolvers
for relations and improve querying for the include filter. The findByForeignKeys
method finds model instances that contain any of the provided foreign key values. You can see more details for findByForeignKeys
in PR #3473. The includeRelatedModels
function returns model instances that include related models that have a registered resolver. You can see more about this function in PR #3517.
We improved DefaultCrudRepository
to leverage InclusionResolver
in PR #3583.
findById
, find
, and findOne
methods have been modified to to call includeRelatedModels
.registerInclusionResolver
method has been added to register resolvers.Tests in repository package for relations have been refactored so that they can be tested against MySQL and MongoDB. This ensures our relations test suites work against real databases, along with the memory. Check PR #3538 for more details.
This month we released the experimental version of @loopback/authorization
by landing Raymond's authorization feature PR.
The authorization system is verified and tested by a PoC PR in the shopping example. Developers can decorate their endpoints with @authorize()
, and provide the authorization metadata like scope
, resource
, voter
in the decorator. Then define or plugin their own authorizers which determine whether a user has access to the resource. This is similar to how the authentication strategies are provided in the authentication system.
@loopback/authentication
and @loopback/authorization
are combined in a way that the authentication
module establishes the user identity from a request, passes it as the current user to the authorization
module which decides is the resource accessible by that user.
Since the two modules share the identity abstracts to describe the user(or application, device in the future), we extracted the user related binding keys and types into a separate module @loopback/security
.
After the initial release of @loopback/authorization
, we received community feedback regarding more flexible decision logic for the voters/authorizers. Raymond introduced two options to configure the decision maker:
precedence
: Controls if Allow/Deny vote takes precedence and override other votes. If not set, default to AuthorizationDecision.DENY
. Once a vote matches the precedence
, it becomes the final decision. The rest of votes will be skipped.
defaultDecision
: Default decision if all authorizers vote for ABSTAIN
. If not set, default to AuthorizationDecision.DENY
.
For details of how the final decision is made, you can check the Decision matrix.
As mentioned in the previous section, the interface UserProfile
and AuthenticationBindings.CURRENT_USER
have been moved into a new module called @loopback/security
. This module contains the common types/interfaces for LoopBack 4 security including authentication and authorization.
In this package we defined interface Principle
as the base type of all the identity models, like UserProfile
, Organization
, Application
(the later two are still in build), it has a symbol field called securityId
, which is used as the identity of a user/application/device.
This month we started to work on the Epic that will simplify building REST APIs in LoopBack 4.
Quoting from https://github.com/strongloop/loopback-next/issues/2036:
In LoopBack 3, it was very easy to get a fully-featured CRUD REST API with very little code: a model definition describing model properties + a model configuration specifying which datasource to use.
Let's provide the same simplicity to LB4 users too.
- User creates a model class and uses decorators to define model properties. (No change here.)
- User declaratively defines what kind of data-access patterns to provide (CRUD, KeyValue, etc.) and what datasource to use under the hood.
@loopback/boot
processes this configuration and registers appropriate repositories & controllers with the app.
The first step was building a new component that will provide the default CRUD REST operations. The PR#3582 introduces a new package @loopback/rest-crud
that can be already used today as an alternative solution to lb4 controller
.
In the next step, we needed to research the best design for model configuration and booter implementation. The PR#3617 offers a proof-of-concept prototype demonstrating the proposed design. We are excited about the extensibility of the proposed approach, as it will make it very easy for 3rd-party developers (including application developers) to implement their own flavor of Repository and Controller classes to be used with the new Model API booter.
Appsody is an open source project that makes creating cloud native applications simple. It provides application stacks for open source runtimes and frameworks, which are pre-configured with cloud native capabilities for Kubernetes and Knative deployments.
This month, LoopBack has been added as one of the application stacks in Appsody to provide a powerful solution to build open APIs and Microservices in TypeScript. You can try its image in stack Node.js-LoopBack
In PR#3539 we added validator ajv-keywords
to validate the incoming request data according to its corresponding OpenAPI schema. Now you can specify ajvKeywords
as true
or an array of AJV validation keywords in the validation options. See examples:
app = new RestApplication({rest: givenHttpServerConfig()});
app
.bind(RestBindings.REQUEST_BODY_PARSER_OPTIONS)
.to({validation: {ajvKeywords: true}});
or
app = new RestApplication({rest: givenHttpServerConfig()});
app
.bind(RestBindings.REQUEST_BODY_PARSER_OPTIONS)
.to({validation: {ajvKeywords: ['range']}});
If multiple operations are executed by a connector before a connection is established with the database, these operation are queued up. If the number of queued operations exceeds Node.js' default max listeners amount, it throws the following warning: MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 connected listeners added. Use emitter.setMaxListeners() to increase limit
. We introduced a default value for the maximum number of emitters and now allow users to customize the number. In a LoopBack 3 application's datasources.json
, you can change the number by setting the maxOfflineRequests
property to your desired number. See PR #1767 and PR #149 for details.
In PR#3618 we added support for applying multiple @bind()
decorators on the same class. Now you can decorator your class like:
@bind({scope: BindingScope.TRANSIENT})
@bind({tags: {name: 'my-controller'}})
export class MyController {
// Your controller members
}
The architecture diagrams are added for concepts Binding, Component, Context, IoC Container, and Context Hierarchy.
We also reorganized the documentation so that it's easier for users to understand, including:
As the summer ends, Nora wraps up her 12-month internship in the LoopBack team. She has made good progress in the inclusion of related models story this month! While she has returned to school, Nora will still be maintaining LoopBack on a part-time basis. We send her best wishes at school, and sincere thank yous for her dedication and hard work!
On the other hand, we're excited to have Deepak back to the team. Bringing his knowledge and experience in cloud and container related technologies, it would be a great addition to us. Welcome Deepak!
LoopBack's success depends on you! We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Here's how you can join us and help the project:
Now our project is on IBM Cloud. But you may notice there is not anything that we can actually play with. It's just some APIs. How can we call it a game without front-end UI?
In this episode, I will build signup, login, and home pages with React.
You can check here for the code from this episode.
In this series, I’m going to help you learn LoopBack 4 and how to use it to easily build your own API and web project. We’ll create a new project I’ve been thinking about: an online web text-based adventure game. In this game, you can create your own account to build characters, fight monsters and find treasures. You will be able to control your character to take a variety of actions: attacking enemies, casting spells, and getting loot. This game also allows multiple players to log in and play with their friends.
In the last episode, we covered how to run our project in Docker and push it to Kubernetes cluster on IBM Cloud.
Here are the previous episodes:
I am completely new to the front-end world. So I took some online courses. If you don't have any front-end experience like me, you should spend some time on the basic knowledge before moving on.
You don't need to finish them all. Watching online courses is boring. You can start to write code whenever you think you are ready.
I will also use some other libraries.
You don't have to fully understand them before. I will show you how to use them step by step.
Install create-react-app
npm i create-react-app
Then run this to create a new react project:
create-react-app <your_project_name>
If you go to the project you just created and run npm start
, you will see a page like this:
Before we start, we need to spend some time on the project structure.
In a React project, everything is component. Your pages, navigation bar, input form, or even a button, all of them could be components. All of those components are organized in a tree structure. Here is my project structure.
And here is my directory structure:
App.jsx
First, open the src/App.js
file. It will be the parent of all other components.
Change your App.js
to App.jsx
. It makes our life easier to use .jsx
in React.
Then change your App.jsx
to this:
import React, { Component } from "react";
import { BrowserRouter as Router, Redirect, Route } from "react-router-dom";
import { NavBar } from "./components";
import { Login, Signup, HomePage } from "./containers";
import { userService, authenticationService } from "./services";
import "./containers/style.css";
class App extends Component {
constructor(props) {
super(props);
this.state = {
currentUser: "",
data: {},
gear: {}
};
this.handelLogout = this.handelLogout.bind(this);
this.handelUserData = this.handelUserData.bind(this);
}
componentDidMount() {
this.handelUserData();
}
handelLogout() {
authenticationService.logout();
this.setState({ currentUser: "", data: {}, gear: {} });
}
handelUserData() {
const currentUser = localStorage.getItem("currentUser");
if (currentUser) {
this.setState({ currentUser });
userService.getUserData(currentUser, this);
userService.getGearData(currentUser, this);
}
}
render() {
const { gear, data, currentUser } = this.state;
return (
<div className="jumbotron">
<NavBar data={data} onLogout={this.handelLogout} />
<div className="container basic">
<div className="col-sm-8 col-sm-offset-2 basic">
<Router>
<div>
<Route
exact
path="/"
render={props =>
localStorage.getItem("currentUser") ? (
<HomePage
{...props}
currentUser={currentUser}
data={data}
gear={gear}
handelUserData={this.handelUserData}
/>
) : (
<Redirect
to={
{
pathname: "/login",
state: { from: props.location }
}
}
/>
)
}
/>
<Route
path="/login"
render={props => (
<Login {...props} handelUserData={this.handelUserData} />
)}
/>
<Route path="/signup" component={Signup} />
</div>
</Router>
</div>
</div>
<div className="text-center">
<p>
<a href="https://loopback.io/">Powered by Loopback 4</a>
</p>
<p>
<a href="https://github.com/gobackhuoxing/first-web-game-lb4">
Github@gobackhuoxing
</a>
</p>
</div>
</div>
);
}
}
export { App };
Here we will use a library called react-router-dom. Simply run npm install react-router-dom
to install it. This library allows us to navigate between different components.
Let's go through this line by line.
The first thing you can see is:
this.state = {
currentUser: "",
data: {},
gear: {}
};
This is the state of this component. Because we are using JWT
in back-end for login. we need to store the token for future API calls. We also need to store the basic user information, so we can display it somewhere.
Then we have three functions:
componentDidMount() {
this.handelUserData();
}
handelLogout() {
authenticationService.logout();
this.setState({ currentUser: "", data: {}, gear: {} });
}
handelUserData() {
const currentUser = localStorage.getItem("currentUser");
if (currentUser) {
this.setState({ currentUser });
userService.getUserData(currentUser, this);
userService.getGearData(currentUser, this);
}
}
handelLogout
is a function to logout. It will remove our token from localStorage
and user data from state
.handelUserData
is a function to fetch user data from back-end and store the data in state
. In react, never change state
directly. If you do that, React will not update the page, because it doesn't know what has been changed. You should always use setState()
to change state
so that React can update all pages that related to this change.authenticationService
and userService
are my self-defined services to do all of the API calls.componentDidMount
is a react build-in function that will be executed after the first render. I use it to get user data before page loading. You can check here for more information about the React component life cycle.The render
function defined how does our component look like. I have four children components here: NavBar
, HomePage
, Login
, and Signup
.
We use react-router-dom
for redirecting. I have three pages in my route:
<Route
exact
path="/"
render={props =>
localStorage.getItem("currentUser") ? (
<HomePage
{...props}
currentUser={currentUser}
data={data}
gear={gear}
handelUserData={this.handelUserData}
/>
) : (
<Redirect
to={
{
pathname: "/login",
state: { from: props.location }
}
}
/>
)
}
/>
If we can find token in localStorage
, we will go to HomePage
, otherwise, we will go to Login
.
You can check here for my code of App.jsx
.
A container is also a component. It is also a holder for other components. We have three containers: HomePage
, Login
, Signup
.
Let's first create a containers
folder in /src
.
This is my Login.jsx
.
import React, { Component } from "react";
import { authenticationService } from "../services";
class Login extends Component {
unmount = false;
constructor(props) {
super(props);
this.state = {
email: "",
password: "",
error: "",
submitted: false,
loading: false,
shortPassword: false
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
componentWillUnmount() {
this.unmount = true;
}
handleChange(e) {
const { name, value } = e.target;
this.setState({ [name]: value });
}
handleSubmit = e => {
e.preventDefault();
const { email, password } = this.state;
const { handelUserData } = this.props;
this.setState({ submitted: true });
if (!(email && password)) {
return;
}
if (password.length < 8) {
this.setState({ shortPassword: true });
return;
}
this.setState({ loading: true, shortPassword: false });
authenticationService.login(email, password, this).then(
function() {
if (!this.unmount) this.setState({ loading: false });
}.bind(this)
);
handelUserData();
};
render() {
const {
email,
password,
error,
loading,
submitted,
shortPassword
} = this.state;
return (
<React.Fragment>
<h2>Login</h2>
{error && error.response.data.error.statusCode === 404 && (
<div className={"alert alert-danger"}>Email doesn't exist</div>
)}
{error && error.response.data.error.statusCode === 401 && (
<div className={"alert alert-danger"}>Password is incorrect</div>
)}
{error && error.response.data.error.statusCode === 422 && (
<div className={"alert alert-danger"}>
Email or Password is invalid
</div>
)}
<form name="form" onSubmit={this.handleSubmit}>
<div className="form-group">
<label htmlFor="email">Email</label>
<input
type="email"
className="form-control"
name="email"
value={email}
onChange={this.handleChange}
/>
</div>
{submitted && !email && (
<div className="alert alert-danger">Email is required</div>
)}
<div className="form-group">
<label htmlFor="password">Password</label>
<input
type="password"
className="form-control"
name="password"
value={password}
onChange={this.handleChange}
/>
</div>
{submitted && !password && (
<div className="alert alert-danger">Password is required</div>
)}
{submitted && shortPassword && (
<div className="alert alert-danger">
Password too short - minimum length is 8 characters
</div>
)}
<div className="form-group">
<button className="btn btn-primary" disabled={loading}>
Login
</button>
</div>
<div>
Don't have an account? <a href="/signup"> SignUp!</a>
</div>
</form>
</React.Fragment>
);
}
}
export { Login };
I have an input form to collect data from users and pass that data to back-end. After the user hit the Login
button, this handleSubmit
function will be called.
handleSubmit = e => {
e.preventDefault();
const { email, password } = this.state;
const { handelUserData } = this.props;
this.setState({ submitted: true });
if (!(email && password)) {
return;
}
if (password.length < 8) {
this.setState({ shortPassword: true });
return;
}
this.setState({ loading: true, shortPassword: false });
authenticationService.login(email, password, this).then(
function() {
if (!this.unmount) this.setState({ loading: false });
}.bind(this)
);
handelUserData();
};
It basically validates all the user input and action. If everything looks good, it will pass the user's email and password to back-end, otherwise, it will tell the user there is something wrong.
It also uses authenticationService
for login API call. We will talk about that later. The Signup
page is almost the same. You can check my repo for more details.
After login, the user will be navigated to HomePage
.
import React, { Component } from "react";
import { Display, InitCharacter } from "../components";
import "./style.css";
class HomePage extends Component {
componentDidMount() {
const { handelUserData, data } = this.props;
if (!data) this.props.history.push("/login");
handelUserData();
}
render() {
const { data, gear, currentUser, handelUserData } = this.props;
return (
<React.Fragment>
<div className="basic">
{data.name !== "nousername" && (
<h2>
LV.{data.level} {data.name}
</h2>
)}
{data.name !== "nousername" && (
<Display className="basic" data={data} gear={gear} />
)}
{data.name === "nousername" && (
<InitCharacter
className="basic"
currentUser={currentUser}
data={data}
handelUserData={handelUserData}
/>
)}
</div>
</React.Fragment>
);
}
}
export { HomePage };
We have two children components in HomePage
:
InitCharacter
to create a new character if this is the user's first time login.Display
to display the user's character information, if the user already has one.{
data.name !== "nousername" && (
<Display className="basic" data={data} gear={gear} />
);
}
{
data.name === "nousername" && (
<InitCharacter
className="basic"
currentUser={currentUser}
data={data}
handelUserData={handelUserData}
/>
);
}
We will store the user's information in the state
of App
. If the user doesn't have a character name, we will show Display
component, otherwise, we will show InitCharacter
component.
You can check here for the code of containers
.
Create a component
folder in src
. We will put all re-useable components here.
import React, { Component } from "react";
import { userService, gearList } from "../services";
import "./style.css";
class InitCharacter extends Component {
unmount = false;
constructor(props) {
super(props);
this.state = {
name: "",
error: null,
submitted: false,
loading: false,
lastClick: null
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
componentWillUnmount() {
this.unmount = true;
}
handleChange(e) {
const { name, value } = e.target;
this.setState({ [name]: value });
}
handleSubmit = e => {
e.preventDefault();
const { name, lastClick } = this.state;
const { currentUser, handelUserData } = this.props;
this.setState({ submitted: true });
if (!name && !lastClick) {
return;
}
this.setState({ loading: true });
let gear = {};
switch (lastClick.id) {
case "1":
gear = {
weapon: gearList.weapons.guideBookJunior,
armor: gearList.armors.silkRobe,
skill: gearList.skills.sacrifice
};
break;
case "2":
gear = {
weapon: gearList.weapons.surgicalDagger,
armor: gearList.armors.labCoat,
skill: gearList.skills.bloodLetting
};
break;
case "3":
gear = {
weapon: gearList.weapons.rustyShortSword,
armor: gearList.armors.chainArmor,
skill: gearList.skills.slap
};
break;
default:
break;
}
userService.initCharacter(currentUser, name, gear, this).then(function() {
handelUserData();
});
};
handelClick = e => {
const { lastClick } = this.state;
e.target.classList.toggle("open");
if (lastClick) lastClick.classList.toggle("open");
if (!this.unmount) this.setState({ lastClick: e.target });
};
render() {
const { name, submitted, loading } = this.state;
return (
<React.Fragment>
<div className="panels">
<div id="1" className="panel panel1" onClick={this.handelClick}>
<p className="classes">Demon Scholar</p>
</div>
<div id="2" className="panel panel2" onClick={this.handelClick}>
<p className="classes">Plague Doctor</p>
</div>
<div id="3" className="panel panel3" onClick={this.handelClick}>
<p className="classes knight">Knight of Madness</p>
</div>
</div>
<form name="form" onSubmit={this.handleSubmit}>
<div className="form-group">
<label className="text" htmlFor="name">
Character Name
</label>
<input
type="name"
className="form-control"
name="name"
value={name}
onChange={this.handleChange}
/>
{submitted && !name && (
<div className="alert alert-danger">
Character name is required
</div>
)}
</div>
<div className="form-group button">
<button className="btn btn-primary button" disabled={loading}>
Start
</button>
</div>
</form>
</React.Fragment>
);
}
}
export { InitCharacter };
I have three classes for a user to choose from. If the user clicks one of the classes, the handelClick
function will store that one in state
. Then we call userService.initCharacter
to create a new character.
If the user has one character, we will jump to Display
page to show all of the user information.
import React, { Component } from "react";
import { DropdownButton, Dropdown, Table } from "react-bootstrap";
import "./style.css";
class Display extends Component {
render() {
const { data, gear } = this.props;
return (
<React.Fragment>
<Table striped bordered hover variant="dark">
<tbody>
<tr>
<th>EXP</th>
<th>
{data.currentExp}/{data.nextLevelExp}
</th>
</tr>
<tr>
<th>HP</th>
<th>
{data.currentHealth}/{data.maxHealth}
</th>
</tr>
<tr>
<th>MP</th>
<th>
{data.currentMana}/{data.maxMana}
</th>
</tr>
<tr>
<th>Attack</th>
<th>{data.attack}</th>
</tr>
<tr>
<th>defence</th>
<th>{data.defence}</th>
</tr>
<tr>
<th>
<DropdownButton
title="weapon"
variant="danger"
id="weapon"
key="weapon"
>
{gear[0] === "no weapon" && (
<Dropdown.Item eventKey="1"> {gear[0]}</Dropdown.Item>
)}
{gear[0] && gear[0] !== "no weapon" && (
<Dropdown.Item eventKey="1">{gear[0].name} </Dropdown.Item>
)}
{gear[0] && gear[0] !== "no weapon" && (
<Dropdown.Item eventKey="1">
Attack: {gear[0].attack}{" "}
</Dropdown.Item>
)}
{gear[0] && gear[0] !== "no weapon" && (
<Dropdown.Item eventKey="1">
Defence: {gear[0].defence}{" "}
</Dropdown.Item>
)}
</DropdownButton>
</th>
<th>
<DropdownButton
title="armor"
variant="primary"
id="armor"
key="armor"
>
{gear[1] === "no armor" && (
<Dropdown.Item eventKey="1"> {gear[1]}</Dropdown.Item>
)}
{gear[1] && gear[1] !== "no armor" && (
<Dropdown.Item eventKey="1">{gear[1].name} </Dropdown.Item>
)}
{gear[1] && gear[1] !== "no armor" && (
<Dropdown.Item eventKey="1">
Attack: {gear[1].attack}{" "}
</Dropdown.Item>
)}
{gear[1] && gear[1] !== "no armor" && (
<Dropdown.Item eventKey="1">
Defence: {gear[1].defence}{" "}
</Dropdown.Item>
)}
</DropdownButton>
</th>
<th>
<DropdownButton
title="skill"
variant="success"
id="skill"
key="skill"
>
{gear[2] === "no skill" && (
<Dropdown.Item eventKey="2"> {gear[2]}</Dropdown.Item>
)}
{gear[2] && gear[2] !== "no skill" && (
<Dropdown.Item eventKey="1">{gear[2].name} </Dropdown.Item>
)}
{gear[2] && gear[2] !== "no skill" && (
<Dropdown.Item eventKey="1">
Attack: {gear[2].attack}{" "}
</Dropdown.Item>
)}
{gear[2] && gear[2] !== "no skill" && (
<Dropdown.Item eventKey="1">
Cost: {gear[2].cost}{" "}
</Dropdown.Item>
)}
</DropdownButton>
</th>
</tr>
</tbody>
</Table>
</React.Fragment>
);
}
}
export { Display };
In HomePage
, we passed data
and gear
to Display
like this:
<Display className="basic" data={data} gear={gear} />
Then, in Display
, we receive them by using props
const { data, gear } = this.props;
Here I use a library called react-bootstrap
to decorate this component. Feel free to use anything you like. That is an advantage of React. You can use almost any library you want with React.
Create services
folder in src
. Here is where all API calls happen.
authenticationService
authenticationService
is for all authentication API calls.
import { apiService } from "./APIServices";
export const authenticationService = {
login,
signup,
logout
};
const axios = require("axios");
function login(email, password, self) {
const data = {
email: email,
password: password
};
const header = {
"Content-Type": "application/json"
};
return axios
.post(apiService.login, data, header)
.then(function(response) {
localStorage.setItem("currentUser", response.data.token);
self.props.history.push("/");
})
.catch(function(error) {
if (!self.unmount) self.setState({ error });
});
}
function signup(email, password, self) {
const header = {
"Content-Type": "application/json"
};
const data = {
email: email,
password: password,
name: "nousername"
};
return axios
.post(apiService.character, data, header)
.then(function() {
self.props.history.push("/login");
})
.catch(function(error) {
if (!self.unmount) self.setState({ error });
});
}
function logout() {
console.log("logout");
localStorage.removeItem("currentUser");
}
We are using a library called axios to do all API calls. The basic syntax is like this:
axios.get/post/put/patch(<Your_URL>,<Your_body>,<your_header>)
In the login function:
axios
.post(apiService.login, data, header)
.then(function(response) {
localStorage.setItem("currentUser", response.data.token);
self.props.history.push("/");
})
.catch(function(error) {
if (!self.unmount) self.setState({ error });
});
We store the token in localStorage
. Then we jump to the HomePage
. self.props.history.push("/")
is how we navigate between different components by using react-router-dom
.
userService
userService
handles all API calls that related to users, like change name and fetch user data.
import { authenticationService } from "./AuthServices";
import { apiService } from "./APIServices";
export const userService = {
getUserData,
getGearData,
changeCharacterName,
initCharacter,
changeWeapon,
changeArmor,
changeSkill
};
const axios = require("axios");
function getUserData(currentUser, self) {
axios
.get(apiService.character, {
headers: { Authorization: `Bearer ${currentUser}` }
})
.then(function(response) {
self.setState({ data: response.data });
})
.catch(function() {
authenticationService.logout();
});
}
function getGearData(currentUser, self) {
axios
.get(apiService.updatecharacter, {
headers: { Authorization: `Bearer ${currentUser}` }
})
.then(function(response) {
self.setState({ gear: response.data });
})
.catch(function() {
authenticationService.logout();
});
}
function changeCharacterName(currentUser, name, self) {
const data = {
name: name
};
axios
.patch(apiService.changename, data, {
headers: { Authorization: `Bearer ${currentUser}` }
})
.catch(function(error) {
if (error.response && error.response.data.error.statusCode === 401)
authenticationService.logout();
else if (!self.unmount) self.setState({ error });
});
}
function initCharacter(currentUser, name, gear, self) {
const data = {
name: name,
gear
};
return axios
.patch(apiService.initCharacter, data, {
headers: { Authorization: `Bearer ${currentUser}` }
})
.catch(function(error) {
if (error.response && error.response.data.error.statusCode === 401)
authenticationService.logout();
else if (!self.unmount) self.setState({ error });
});
}
function changeWeapon(currentUser, gear, self) {
axios
.patch(apiService.updateweapon, gear.weapon, {
headers: { Authorization: `Bearer ${currentUser}` }
})
.catch(function(error) {
if (error.response && error.response.data.error.statusCode === 401)
authenticationService.logout();
else if (!self.unmount) self.setState({ error });
});
}
function changeArmor(currentUser, gear, self) {
axios
.patch(apiService.updatearmor, gear.armor, {
headers: { Authorization: `Bearer ${currentUser}` }
})
.catch(function(error) {
if (error.response && error.response.data.error.statusCode === 401)
authenticationService.logout();
else if (!self.unmount) self.setState({ error });
});
}
function changeSkill(currentUser, gear, self) {
axios
.patch(apiService.updateskill, gear.skill, {
headers: { Authorization: `Bearer ${currentUser}` }
})
.catch(function(error) {
if (error.response && error.response.data.error.statusCode === 401)
authenticationService.logout();
else if (!self.unmount) self.setState({ error });
});
}
You can check here for the code of Services
.
In this episode, we covered how to create simple login, signup and home pages with React. We also learned how to connect front-end and back-end. React is the most popular front-end framework today. You can easily use it to create your own front-end UI. It doesn't have to be React and LoopBack. The basic idea is similar.
Next time, we will extend our project on back-end APIs. So we can actually have something to play as a game.
In the meantime, learn more about LoopBack in past blogs.
]]>Previously, we looked at how to add a global interceptor. In this article, we are going to build an application that validates the incoming request using class level and method level interceptors
For the complete application, you can go to this repo: https://github.com/dhmlau/loopback4-interceptors
If you want to skip this part, you can check out the orginal-app
branch of this github repo: https://github.com/dhmlau/loopback4-interceptors/tree/original-app.
If you want to follow along, take these steps.
Scaffold the app by calling lb4 app loopback4-interceptors --yes
In the newly created project, create a file called order.json
with the following content:
{
"name": "Order",
"base": "Entity",
"properties": {
"orderNum": {
"type": "string",
"id": true,
"required": true
},
"customerNum": {
"type": "string",
"required": true
},
"customerEmail": {
"type": "string",
"required": true
},
"total": {
"type": "number",
"required": true
}
}
}
Create the Order model by providing the above json file. Run lb4 model --config order.json --yes
Create a DataSource with the in-memory connector. Run lb4 datasource ds
. Then select "In-memory db" as the type of connector.
Create a Repository and accept all defaults by running lb4 repository
command.
Finally, create a controller. Follow the prompts as below:
$ lb4 controller
? Controller class name: Order
Controller Order will be created in src/controllers/order.controller.ts
? What kind of controller would you like to generate? REST Controller with CRUD functions
? What is the name of the model to use with this CRUD repository? Order
? What is the name of your CRUD repository? OrderRepository
? What is the type of your ID? string
? What is the base HTTP path name of the CRUD operations? /orders
create src/controllers/order.controller.ts
update src/controllers/index.ts
Controller Order was created in src/controllers/
You now have a LoopBack application ready to run.
What we're doing here is validating user inputs at the REST layer. Note that in real world, you should also consider validating data at the Repository level to ensure the validation is applied even when modifying data from places outside of controllers, e.g. tests or services running in background.
For the POST /order
endpoint, we are going to validate the order before actually creating the order. The length of orderNum
has to be 6, otherwise the order is not valid.
Let's define the interceptor function in the OrderController
class just before the class is defined.
In src/controllers/order.controller.ts
:
Add this statement:
import {intercept, Interceptor} from '@loopback/core';
Add the following function to validate order.
const validateOrder: Interceptor = async (invocationCtx, next) => {
console.log('log: before-', invocationCtx.methodName);
const order: Order = new Order();
if (invocationCtx.methodName == 'create')
Object.assign(order, invocationCtx.args[0]);
else if (invocationCtx.methodName == 'updateById')
Object.assign(order, invocationCtx.args[1]);
if (order.orderNum.length !== 6) {
throw new HttpErrors.InternalServerError('Invalid order number');
}
const result = await next();
return result;
};
@intercept
Decorator at the Method LevelAfter defining the interceptor function, you can now use this as a method-level or class-level decorator. For class-level interceptor, you just apply it on the class, like this.
@intercept(validateOrder)
export class OrderController {
//...
}
However, we want the validation to run only when the order is being created, so the @intercept
decorator will be applied at the method level. To do this, add the @intercept
decorator on the method I want to intercept, i.e. the POST
method:
```ts
@intercept(validateOrder) // <--- add here
@post('/orders', {
responses: {
'200': {
description: 'Order model instance',
content: {'application/json': {schema: {'x-ts-type': Order}}},
},
},
})
async create(@requestBody() order: Order): Promise<Order> {
```
Now that the application is ready to go, start the app by running npm start
command and go to the API Explorer: http://localhost:3000/explorer.
GET /orders/count
Since this method doesn't have the interceptor, when this endpoint is being called, there shouldn't be anything printed to the log.
POST /orders
This is the method where the interceptor is applied to validate the order. Let's give it a try with a valid order.
{
"orderNum": "111111",
"customerNum": "12345",
"customerEmail": "aa@abc.com",
"total": 100
}
You should expect to get the HTTP 200 response code that the order has been created successfully.
Now try another order with orderNum
is 11
.
{
"orderNum": "11",
"customerNum": "67890",
"customerEmail": "bb@abc.com",
"total": 1000
}
You should expect a HTTP 500 error with message saying "Internal Server Error".
LoopBack's success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Please join us and help the project by:
We're excited to congratulate the LoopBack team for earning the 2019 API Award for the "Best in API Middleware" category. LoopBack is a highly extensible, open source Node.js framework based on Express that enables you to quickly create dynamic end-to-end REST APIs and connect to backend systems such as databases and SOAP or REST services.
The 2019 API Awards celebrate the technical innovation, adoption, and reception in the API & Microservices industries and use by a global developer community. The 2019 API Awards will be presented at the 2019 API Awards Ceremony during the first day of API World 2019 (Oct 8-10, 2019, San Jose Convention Center), the world’s largest API & Microservices conference & expo -- the largest event for the API economy -- in its 8th year, with over 3,500 attendees.
The 2019 API Awards received hundreds of nominations, and the Advisory Board to the API Awards selected LoopBack based on three criteria:
"IBM is a shining example of the API technologies now empowering developers & engineers to build upon the backbone of the multi-trillion-dollar market for API-driven products and services. Today’s cloud-based software and hardware increasingly runs on an open ecosystem of API-centric architecture, and IBM’s win here at the 2019 API Awards is evidence of their leading role in the growth of the API Economy,” said Jonathan Pasky, Executive Producer & Co-Founder of DevNetwork, producer of API Word & the 2019 API Awards.
The LoopBack team is proud that all of their hard work on LoopBack is being recognized by the larger Node.js community.
"We're thrilled and honored to receive the Best in API Middleware 2019 award from API World," said Raymond Feng, co-creator and architect for LoopBack. "It's indeed a great recognition and validation of the LoopBack framework, team and community."
Feng continued, "Six and half years ago, the team created LoopBack at StrongLoop with the goal to help fellow developers kick off their API journey with the ideal Node.js platform. With the support of the fantastic Node.js ecosystem, the team built on top of open source modules such as Express and made it incredibly simple to create REST APIs out of existing datasources and services."
"The StrongLoop team's bet on open APIs and Node.js was right. The project and community have grown significantly."
"The StrongLoop team joined with IBM API Connect team in 2015 to better position LoopBack as a strategic open source project. LoopBack 4 is the second generation of the framework. Version 4 incorporates what the team has learned with new standards and technologies such as TypeScript, OpenAPI, GraphQL, and cloud-native microservices to build a foundation for even greater flexibility, extensibility, composablity, and scalability."
"More and more features are shipped and being built by us and the community. The LoopBack team strive to bring best practices and tools. We love Github stars. It's simply rewarding to create something valuable for the open source community!" concluded Feng.
See the official announcement and read about other winners here.
You can help shape the future of LoopBack with your support and engagement! You can help make LoopBack even better and meaningful for your API creation experience by:
This July 20th was the 50th anniversary of The Moon Landing and the famous quote, "That's one small step for man, one giant leap for mankind." This great, memorable event reminds me that every task the LoopBack team finishes will end up enhancing our project. From starting to build new features such as inclusion
, to enriching the documentation, we believe that we are making LoopBack better by taking all these small steps!
We finished up 85 story points this month. See the July milestone for an overview of what we have worked on, and read on for more details.
The original CLI version is now stored in the .yo.rc.json
file. This allows users to check the version of CLI when upgrading their dependencies. See PR #3338 for details.
We refactored the way that the property generator is invoked in the model generator. Now when generating a new model, the loop prompts for adding properties are more robust. See PR #412 for more information.
For the CLI lb4 app
can now handle a hyphened path and will generate default names properly. See PR #2092 for more details.
@config
DecoratorIn PR #3329, we introduce new config metadata, which allows @config.*
to be resolved from a binding other than the current one. For example, before we could inject @config
this way:
export class MyRestServer {
constructor(
// injects `RestServerConfig.port` to the target
@config('host')
host: string,
)
//...
}
Now this kind of injection can be done this way:
export class MyRestServer {
constructor(
// Inject the `rest.host` from the application config
@config({fromBinding: 'application', propertyPath: 'rest.host'})
host: string,
)
}
The module Context
didn't use the invocation context to resolve parameter injection. This might limit some use cases such as @inceptors
which couldn't rebind new values. With this implementation, you can use options.skipParameterInjection
to resolve parameter injection.
We enabled the exclude
and optional
options to JsonSchemaOptions
.
The exclude
option takes in an array of model properties which you can exclude from your requestBody
. For example, you can exclude the id
property from the body of a POST
request:
POST /todos
async create(
@requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(Note, {exclude: ['id']}),
},
},
})
note: Omit<Note, 'id'>,
): Promise<Note> {
// ...
}
Any new generated controllers scaffolded by lb4 controller
will have the id
property excluded by default. See PR #3297 for details.
We also added support for excluding a custom primary key name (not the default id
) in PR #3347.
The optional
option takes in an array of model properties which you can mark optional in your requestBody
. For example, you can mark the foreign key property from the body of a POST
request as optional:
POST /todo-lists/{id}/todos
async create(
@param.path.number('id') id: number,
@requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(Todo, {
exclude: ['id'],
optional: ['todoListId'],
}),
},
},
})
todo: Omit<Todo, 'id'>,
): Promise<Todo> {
// ...
}
If this option is set and is not empty, it will override the partial
option. See PR #3309 for details.
The current @requestBody()
only takes in an entire request body (with very nested content object) or infers the schema from the parameter type. To simplify the signature so that users can describe the schema with concise configure options, we explored a new signature in a spike story: @requestBody(spec, model, schemaOptions)
. Most of the discussions are tracked in PR #3398, and additional opinions and feedback are welcomed before we start implementing the simplified decorator.
To provide observability for LoopBack 4 applications deployed to a cloud environment, we start to add more integrations to expose health, metrics, and distributed tracing data. These packages are positioned as extensions to the framework.
The Health extension has been released as an experimental feature. Two more PRs are up for review:
@model
and @property
DecoratorsAs we always try to beef up the documentation for LB4, this month we improved docs of the decorators @model
and @property
. Before we were simply using docs from LB3 site. However, since the implementation structures are different for LB3 and LB4, some parts of the docs are not precise or correct for LB4. We updated the documentation with more details and pointed out the differences between LB3 and LB4. We also added more links so that users can find more references and join the dicussion on GitHub with us. More details are in PR #3354.
We also worked on bridging the gap between LoopBack 3 and 4 in terms of exposing transactions from repositories in PR #3397. We've introduced sugar API beginTransaction()
at the repository level, which delegates work to the data source that belongs to it to start a transaction.
With it, we've created TransactionalRepository
interface that is meant to be used with connectors that support transactions and DefaultTransactionalRepository
which can be used for CRUD repositories that support transactions. Once a transaction object is obtained from beginTransaction()
, it can be passed into any CRUD calls made for the models attached to the backing datasource. The user can then either call commit()
or rollback()
actions for the transaction object to persist or revert the changes made.
Note that only select SQL connectors support transactions to use this feature. For more information, check out the documentation.
Connector reference pages that were missing in the LoopBack 4 docs have been added now. Check issue #2598 for more details.
This month we continued refactoring the tests for mysql
, mssql
, and oracle
connectors in LoopBack 3 so that they can run the imported juggler tests in both 3.x and 4.x versions. Now our mongodb
, postgresql
, kv-redis
, cloudant
, mysql
, mssql
, and oracle
connectors run shared tests.
We updated our templates and existing examples to leverage getModelSchemaRef
in PR #3402.
We updated our oracle
connector to oracledb
v4.0.0 in PR #186.
We tested/enabled Node.js 12 for some of our connectors.
We fixed an edge case where replaceById
was not working for MongoDB database when the data came from the REST API layer. Check issue #3431 and issue #1759 for more details.
We removed the source code of @loopback/openapi-v3-types
package from our monorepo. This package was deprecated last month in favor of openapi3-ts
and @loopback/openapi-v3
. Check PR #3385 for more details.
We fixed automigrate & autoupdate to wait until the datasource is connected to the database. This addressed a bug in the npm script migrate
(scaffolded by lb4 app
), where errors thrown by the database migration were not caught correctly and thus the script did not indicate the failure via a non-zero exit code. Check issue #1756 for more details.
We improved the type definition of toJSON
helper from @loopback/testlab
to better support union types like MyModel | null
(e.g. as returned by Repository findOne
method). Check PR #3823 for more details.
We fixed REST API to better handle the case where a custom basePath
is configured via app.basePath()
API. As a result, the server
entry in OpenAPI spec now correctly includes the configured base path again. Check PR #3266 for more details.
We fixed a MongoDB connector bug which caused the id from the input data object to be deleted. See issue #3267 for more details.
After many weeks in making, Miroslav finally finished researching how to resolve included models when querying datasources. This concluded the spike issue #2634.
The essence:
InclusionResolver
functions. These functions implement the logic for fetching related models.DefaultCrudRepository
) will handle inclusions by calling resolvers registered for individual relations.TodoRepository
scaffolded in your project) will register inclusion resolvers for relations that are allowed to be traversed.hasMany
, belongsTo
, hasOne
at the time of writing).The spike PR #3387 shows a proof-of-concept implementation and includes high-level description of the proposed design in the SPIKE document, where you can find more details.
Our LoopBack core maintainer Biniam is leaving to join the API Connect team. His hard work and dedication were an important part of our team. We appreciate the inspiration he gave us and all the contributions he's made. We believe that he will do an outstanding job in the next phase of his career!
We have updated our loopback.io website with our users and their testimonials. If you would like to be a part of it, see the details in this GitHub issue.
If you're interested in what we're working on next, you can check out the August milestone.
LoopBack's future success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Here's how you can join us and help the project:
Wondering what an interceptor is in LoopBack 4?
Interceptors are reusable functions to provide aspect-oriented logic around method invocations.
Seems pretty useful, right? There are 3 levels of interceptors: global, class level and method level. In this article, we are going to look into what a global interceptor is and how to use it.
Global interceptors are automatically called for all controller methods. They are called first, before interceptors specified at class and method level. You can insert additional logic before and after method invocation through interceptors. Examples of using global interceptors are caching and authorization.
For the sake of illustration, we'll use global interceptors for logging purposes using the default /ping
endpoint that comes with all scaffolded LB4 application. Note that interceptors are not the best tool for logging. It's usually better to implement logging as a new sequence action, as we demonstrate in our log extension example.
After you've scaffolded the application, run lb4 interceptor
command to create an interceptor. Since we are going to have one global interceptor, leave the group name for the global interceptor
as an empty string which is the default value.
$ lb4 interceptor
? Interceptor name: logging
? Is it a global interceptor? Yes
Global interceptors are sorted by the order of an array of group names bound
to ContextBindings.GLOBAL_INTERCEPTOR_ORDERED_GROUPS. See
https://loopback.io/doc/en/lb4/Interceptors.html#order-of-invocation-for-interceptors.
? Group name for the global interceptor: ('')
create src/interceptors/logging.interceptor.ts
update src/interceptors/index.ts
Interceptor Logging was created in src/interceptors/
Let's take a look at the generated interceptor.
Go to src/interceptors/logging.interceptor.ts
. You'll see two comments in the try-catch block where you can add your logic: pre-invocation and post-invocation. We are going to simply print out the method name of the invocationContext before and after the method is being invoked.
try {
// Add pre-invocation logic here
// ----- ADDED THIS LINE ----
console.log('log: before-' + invocationCtx.targetName);
const result = await next();
// Add post-invocation logic here
// ----- ADDED THIS LINE -----
console.log('log: after-' + invocationCtx.targetName);
return result;
} catch (err) {
// Add error handling logic here
console.error(err);
throw err;
}
That's it! The global interceptor is ready for action.
Start the application with npm start
command. Then go to the API Explorer: http://localhost:3000/explorer.
You'll now see the following printed to the console:
log: before-ExplorerController.prototype.index
log: after-ExplorerController.prototype.index
Next, call the /ping
endpoint by clicking "Try it Out" > "Execute". You'll see two more lines got printed:
log: before-PingController.prototype.ping
log: after-PingController.prototype.ping
The interceptor method was called because it is at the global level.
For more meaningful log messages, you might want to get more information about the HTTP request. To do that, add this import in the interceptor class:
import {RestBindings} from '@loopback/rest';
We're going to print out the endpoint being called. To do that, add the snippet below as the pre-invocation logic:
const httpReq = await invocationCtx.get(RestBindings.Http.REQUEST, {optional: true,});
if (httpReq) {
console.log('Endpoint being called:', httpReq.path);
}
Restart the application, go to API Explorer and call the /ping
endpoint again. You'll see the following printed to the console log:
log: before-ExplorerController.prototype.index
Endpoint being called: /explorer/
log: after-ExplorerController.prototype.index
log: before-PingController.prototype.ping
Endpoint being called: /ping
log: after-PingController.prototype.ping
For other interceptor examples, check out: https://loopback.io/doc/en/lb4/Interceptors.html#example-interceptors
For running applications that use interceptors, see:
LoopBack's future success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Please join us and help the project by:
Now that our project has basic features that allow us to create our own characters and log in, it's time to deploy it to cloud! So, we will first run our project in Docker and then push it to Kubernetes cluster on IBM Cloud.
Docker images are lightweight, portable, and self-sufficient. Once you create a Docker image, you can run it almost everywhere. On the other hand, Kubernetes will handle those high level concepts such as storage, network and scale-up.
You can check here for the code from this episode.
In this series, I’m going to help you learn LoopBack 4 and how to use it to easily build your own API and web project. We’ll create a new project I’ve been thinking about: an online web text-based adventure game. In this game, you can create your own account to build characters, fight monsters and find treasures. You will be able to control your character to take a variety of actions: attacking enemies, casting spells, and getting loot. This game also allows multiple players to log in and play with their friends.
In last episode, we covered how to combine your self-defined authorization strategies and services with @loopback/authentication
and how to apply it to your API.
Here are the previous episodes:
You don't have to fully understand those concepts before we start. I will show you how to use them step by step.
The Illustrated Children's Guide to Kubernetes is a wonderful video on YouTube that can give you a clear idea of what is Kubernetes.
Deploying to Kubernetes on IBM Cloud is a tutorial on the official LoopBack 4 website. Because our project is a bit different and uses MongoDB, we need to set up MongoDB on cloud and connect our project to it.
In Episode 1, we disabled Docker when we created our project. Now we need to manually add the Docker feature.
In your project root, create a file called Dockerfile
.
# Check out https://hub.docker.com/_/node to select a new base image
FROM node:10-slim
# Set to a non-root built-in user `node`
USER node
# Create app directory (with user `node`)
RUN mkdir -p /home/node/app
WORKDIR /home/node/app
# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY --chown=node package*.json ./
RUN npm install
# Bundle app source code
COPY --chown=node . .
RUN npm run build
# Bind to all network interfaces so that it can be mapped to the host OS
ENV HOST=0.0.0.0 PORT=3000
EXPOSE ${PORT}
CMD [ "node", "." ]
Then create a file called .dockerignore
.
node_modules
npm-debug.log
/dist
Dockerfile
and.dockerignore
are two Docker-related files that are provided by LoopBack 4. We will use them to create a Docker image.
Open package.json
, add two lines under scripts
. Those are the commands to build and run Docker image.
"docker:build": "docker build -t firstgame .",
"docker:run": "docker run -p 3000:3000 -d firstgame",
Install Docker if you haven't already.
Run this command to create Docker image.
npm run docker:build
If it succeeds, you will see:
Successfully built 0b2c1ff52a2e
Successfully tagged firstgame:latest
Run this command to show all images:
docker image ls
You should see two images like this:
REPOSITORY TAG IMAGE ID CREATED SIZE
firstgame latest 0b2c1ff52a2e 44 seconds ago 430MB
node 10-slim a41b78200d6f 6 days ago 148MB
Now, our image is ready to run. Run this command to create a container. A container is a running instance of an image.
npm run docker:run
Run this command to show all running containers.
docker ps
You will see:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
88cc8acfbeea firstgame "node ." 5 minutes ago Up 5 minutes 0.0.0.0:3000->3000/tcp friendly_archimedes
Because we didn't specify container's name, Docker randomly assigned one for it.
Run this command to see the log output of your container. Replace <container id>
with your container id. In my case, it is 88cc8acfbeea
.
docker logs <container id>
You should see something like this:
Server is running at http://127.0.0.1:3000
Try http://127.0.0.1:3000/ping
Now, you should be able to open the API explorer: http://127.0.0.1:3000/explorer/
If everything is fine, run this command to stop the image.
docker stop <container id>
We are now ready to push our Docker image to cloud.
Sign up for IBM Cloud and install IBM Cloud CLI.
Run this command to login IBM Cloud.
ibmcloud login
If you are using a federated IBM ID, use this command instead:
ibmcloud login -sso
If you logged in successfully, you will see something like:
API endpoint: https://cloud.ibm.com
Region: us-east
User: wenbo.sun@ibm.com
Account: IBM (114e44f826b74008a2afbf099e6b3561)
Resource group: Default
CF API endpoint:
Org:
Space:
Log in to IBM Cloud Container Registry. This is where we store our Docker image.
ibmcloud cr login
If this succeeds, you will see something like:
Logging in to 'us.icr.io'...
Logged in to 'us.icr.io'.
This is the container registry region you logged into.
After we log in, let's create a new namespace for our project.
ibmcloud cr namespace-add my-lb4-namespace
You can run ibmcloud cr namespace-list
to show all of your namespaces.
Run this command to tag the local docker image with the IBM Cloud container registry.
docker tag <image_name>:<tag> <container_registry_region>/<my_namespace>/<new_image_repo>:<new_tag>
In my case, this command will look like this:
docker tag firstgame:latest us.icr.io/my-lb4-namespace/firstgame-repo:1.0
Then push the local image to the container registry.
docker push us.icr.io/my-lb4-namespace/firstgame-repo:1.0
You will see something like this:
The push refers to repository [us.icr.io/my-lb4-namespace/firstgame-repo]
8f77245a867e: Pushed
f3f824dbea6d: Pushed
637a53e1e6ed: Pushing [==============================> ] 144.1MB/236.6MB
69d1baa1ae3c: Pushed
30cea096009e: Pushed
344e2d688289: Pushed
61cb38befba5: Pushed
aa5a12ea4279: Pushed
6270adb5794c: Pushed
When it is done, run the following command to show images on your container registry.
ibmcloud cr image-list
You should see this:
REPOSITORY TAG DIGEST NAMESPACE CREATED SIZE SECURITY STATUS
us.icr.io/my-lb4-namespace/firstgame-repo 1.0 3c853b97ffec my-lb4-namespace 1 hour ago 144 MB No Issues
The SECURITY STATUS
shows No Issues
. If you get issues here, you may want to check Managing image security with Vulnerability Advisor for more related information.
Lastly, run this command to build Docker image on the container registry. Don't forget the .
at the end.
ibmcloud cr build -t us.icr.io/my-lb4-namespace/firstgame-repo:1.0 .
If you don't have a Kubernetes Cluster yet, login to your IBM Cloud in browser and go to https://cloud.ibm.com/kubernetes/catalog/cluster/create to create a free cluster. It may take a while.
When it is done, run this command to point to Kubernetes cluster. My cluster name is firstgame-cluster
.
ibmcloud cs cluster-config <Cluster Name>
You will see something like this. Copy and run the last line.
OK
The configuration for firstgame-cluster was downloaded successfully.
Export environment variables to start using Kubernetes.
export KUBECONFIG=/Users/xiaocase/.bluemix/plugins/container-service/clusters/firstgame-cluster/kube-config-hou02-firstgame-cluster.yml
Run this command to verify your cluster.
kubectl get nodes
You should see something like this.
NAME STATUS ROLES AGE VERSION
10.47.84.60 Ready <none> 5d v1.13.6+IKS
Now your cluster is ready to use.
Because our project is using MongoDB, we need to set up a MongoDB container and our project container in one Kubernetes pod. A Kubernetes pod is a group of one or more containers. Containers in the same pod will share storage and network.
Let's first create a file called first-game.yaml
in our project root. We will use this yaml
file to specify containers and pod. Check here for more information about Kubernetes yaml
file.
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: firstgame
spec:
replicas: 1
template:
metadata:
labels:
run: firstgame
spec:
containers:
- name: fg
image: us.icr.io/my-lb4-namespace/firstgame-repo:1.0
ports:
- containerPort: 3000
- name: db
image: mongo
ports:
- containerPort: 27017
As you can see, we have two containers. fg
is for our project. db
is for MongoDB. They will be running in the same pod so they can share network and talk to each other.
Run this command to use the yaml
file to create containers and pod:
kubectl create -f first-game.yaml
You will see something like this.
deployment.extensions "firstgame" deleted
wenbo:firstgame wenbo$ kubectl create -f first-game.yaml
deployment.extensions "firstgame" created
Run this command to verify our pod is running:
kubectl get pods
If succeeds, you will see this. The 2/2
means there are two containers running in this pod.
NAME READY STATUS RESTARTS AGE
firstgame-85ccbd5496-6nmvt 2/2 Running 0 1m
Now our application is running on Kubernetes. The next step is to expose it to the public.
kubectl expose deployment firstgame --type=NodePort --port=3000 --name=firstgame-service --target-port=3000
You should see this.
service "firstgame-service" exposed
Run this command to get NodePort for this service.
kubectl describe service firstgame-service
You should see:
Name: firstgame-service
Namespace: default
Labels: run=firstgame
Annotations: <none>
Selector: run=firstgame
Type: NodePort
IP: 172.21.59.175
Port: <unset> 3000/TCP
TargetPort: 3000/TCP
NodePort: <unset> 30776/TCP
Endpoints: 172.30.234.135:3000
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
In my case, the NodePort is 30776
.
The last thing we need is the IP address of our cluster.
ibmcloud ks workers firstgame-cluster
You will get something like this:
ID Public IP Private IP Machine Type State Status Zone Version
kube-bkrq1svd0l5j9p3147ng-mycluster-default-000000e0 173.193.106.101 10.76.216.103 free normal Ready hou02 1.13.8_1529
My cluster IP address is 173.193.106.101
.
Now we should be able to access to our application via http://173.193.106.101:30776
In this episode, we covered how to deploy our project with Docker and Kubernetes on IBM Cloud. Once you create a Docker image, you can run it almost everywhere. You can also push your own project image to other cloud like AWS, Azure, and Google Cloud. It should be very easy.
Next time, we will create a simply front-end UI for our project and do a quick demo.
In the meantime, learn more about LoopBack in past blogs.
]]>For the past few months, we have been focusing on the following stories:
@loopback/authentication
2.0 version.repository-json-schema
package.We have a monthly blog reviewing what we've done in each milestone. To stay tuned, don't forget to follow us on Twitter @StrongLoop.
We have recently released @loopback/authentication
2.0. Now you can secure your endpoints with both passport-based and LoopBack native authentication strategies that implement the AuthenticationStrategy
interface. Our shopping app example has also been updated accordingly to include this capability. More details can be found in this authentication updates blog post.
Besides creating an authorization component to add authorization in your LoopBack 4 application, you can now do so by using interceptors. This tutorial shows how to do it from end to end.
We have set up the foundation for inclusion of related models. We made improvements in our repository-json-schema
package to provide the base for inclusion. We updated our TodoList
example to describe navigational properties. The approach we used is a temporary approach to demonstrate how inclusion would work. You can clone the example to check it out.
As one of the major architectural improvements, the concept of interceptor was introduced. Interceptors are reusable functions that provide aspect-oriented logic around method invocations. They can be applied for various uses. See the interceptors docs page for more details.
Besides the interceptor, LoopBack 4 now has a basic life cycle support. It allows registration of life cycle observers and actions, and there is a command line interface and a booter to make this even easier.
In addition, extensibility has been a great advancement for LoopBack 4. We've added decorator functions to allow you to add extensions even more easily.
If you have existing LoopBack 3 applications, it's a good time to start looking into how to migrate them to LoopBack 4. You can do this incrementally by mounting your LB3 application to LoopBack 4. Read this docs page for steps on how to do that.
There has been a lot of enhancement in our tooling. We covered a new major release (v2.0) of @loopback/build
in our June milestone blog.
We have also introduced the concept of experimental features to LoopBack development. The goal is to be able to release experimental features quickly for early feedback while maintaining high code quality on our production-ready code. For details, see https://github.com/strongloop/loopback-next/blob/labs/base/LABS.md.
Additionally, we have put tremendous effort into fixing the CI errors on the connectors, so that we can help land PRs from the community more quickly and more confidently. We've also reduced the build time in loopback-next which improves our development efficiency.
Lastly, Node.js 12 support has been added to the LoopBack 3 and LoopBack 4 core. The next step is to add Node.js 12 support to the connectors.
Our web site loopback.io now has a new look! We have moved the LoopBack 4 web site content back to our main web site.
We have rebuilt the "Who's using LoopBack" section to showcase our users. If you would like to be a part of it, see the details in this GitHub issue.
We'd like to hear from you! Our Q3 roadmap started with a pull request. We'll continue to create future roadmaps in the form of a PR, so feel free to chime in!
For the next 3 months, we'd like to focus on the following:
There are too many features added and bug fixes that cannot be captured here. Check out our previously published monthly milestone blog posts in Q2 for more details:
LoopBack's future success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Please join us and help the project by:
As the temperature gets warmer the LoopBack team is spending this summer releasing hot deliverables. In June we focused on various enhancements such as releasing version 2.0.0 of @loopback/build
, replacing strong-docs
, and improving @loopback/testlab
. We also focused on authentication, inclusion of related models, and other improvements. You can see the June milestone for an overview of what we have worked on, and read on for more details.
@loopback/build
In the past months, we have significantly evolved our build tooling. The last major change was the switch from tslint
to eslint
for linting. We decided it's time to clean up the code, remove unused parts and release a new major version.
The release introduced the following breaking changes:
lb-apidocs
helper is no longer available.lb-tslint
helper is no longer available.lb-tsc
is no longer choosing outDir
for you, you have to specify it explicitly.lb-tsc es2017
.See the release notes for more details and instructions on migrating your existing projects.
As part of these changes, we removed vulnerable dependencies and thus npm install
in newly scaffolded projects reports zero vulnerabilities 🎉.
strong-docs
with tsdocs
In PR#3055, we replaced strong-docs
with @loopback/tsdocs
. We use @loopback/tsdocs
to generate markdown files on our website. With this change, we changed the home for our API docs; you can visit the new home on our website to see the docs.
This change was a breaking change: as mentioned before, lb-apidocs
is no longer available for use, as it was removed as part of this PR. For alternate solutions, you can use Microsoft's api-extractor and api-documenter.
On the brighter side, by removing strong-docs
we also removed dependencies on 3rd party modules that have known security vulnerabilities but are no longer maintained.
@loopback/testlab
We improved our testing helpers to support Jest testing framework.
PR#3013 fixed typings for itSkippedOnTravis
to remove an implicit dependency on Mocha typings, which was causing conflicts when using our testlab from Jest.
PR#3040 introduced a more generic helper skipOnTravis
which supports any BDD verbs like describe
and it
.
skipOnTravis(it, 'does something', async () => {
// the test code
});
PR#3138 added even more generic helper skipIf
that allows you to skip a test case or a test suite on arbitrary condition.
skipIf(someCondition, it, 'does something', async () => {
// the test code
});
We are also looking into ways to migrate our test suite from Mocha to Jest. Stay tuned for updates!
In PR#120, the shopping cart application was updated to utilize the latest authentication package @loopback/authentication2.x
. You can read more about our latest authentication package in our blog What's New in LoopBack 4 Authentication 2.0.
In PR#2977, we introduced some new documentation to the authentication package we've been updating these past few months. See Authentication for details.
In PR#3046, a new authentication tutorial on How to secure your LoopBack 4 application with JWT authentication was added.
We released the new adapter for passport-based strategies as @loopback/authentication-passport
; now you can follow the guide in Use Passport-based Strategies to learn how to create and register a passport strategy and plug it into the authentication system.
getJsonSchema
EnhancementA community user, @samarpanB, has contributed PR#2975 adding a new option includeRelations
to the helper getJsonSchema
. When the option is enabled, the helper adds navigational properties for inclusion of related models in the emitted model schema.
As part of our Inclusion of Related Models Epic, we updated our TodoList example to also include navigational properties. After the work done in PR#3171, now when getting a Todo
with an inclusion filter, its included TodoList
will be a part of the response and vice versa.
When you call GET todos/2
, you get the following response:
{
"id": 1,
"title": "Take over the galaxy",
"desc": "MWAHAHAHAHAHAHAHAHAHAHAHAHAMWAHAHAHAHAHAHAHAHAHAHAHAHA",
"todoListId": 1
}
And now when you call GET todos/2
with the filter {include: [{relation: 'todo-lists'}]}
, you get the following response:
{
"id": 1,
"title": "Take over the galaxy",
"desc": "MWAHAHAHAHAHAHAHAHAHAHAHAHAMWAHAHAHAHAHAHAHAHAHAHAHAHA",
"todoListId": 1,
"todoList": {
"id": 1,
"title": "Sith lord's check list"
}
}
You can check out the new full example by calling lb4 example todo-list
.
PATCH
In PR#3199, we enabled added a partial
option for JsonSchemaOptions
. This addition allowed us to emit schema where all the model properties are optional. By doing this, this lets us to now do PATCH
requests without having to include all required properties in the request body.
For example, before when trying to update a Todo
from our Todo
example, you'd have to include the title
property in the request body:
PATCH todos/1
{
"title": "Take over the galaxy",
"desc": "get the resources ready"
}
But now even though title
is still required, it is optional when doing a PATCH
request. So now the following is a valid request body to pass to the following request:
PATCH todos/1
{
"desc": "get the resources ready"
}
All newly created projects generated through the CLI will allow partial updates through PATCH
.
In PR#3202, we updated the GitHub issues template, so that when you open a new issue, you're taken to a page (see image below) where you can choose the type of issue to open. The options we offer are: bug report, feature request, question, and security vulnerability. With these new more specific templates, it will be easier for the team to go through and understand new issues.
In PR#2989, we made some improvements to the CLI:
$ lb4 controller
? Controller class name: todo
$ Controller Todo will be created in src/controllers/todo.controller.ts
We also made some fixes to our lb4 discover
command:
modelSettings
that go into the @model
decorator.schema
field in modelSettings
use the owner of the schema.We have a new addition to our LoopBack team: Agnes (@agnes512 on GitHub) has joined the team as our intern for the next year. She has already contributed improvements to our documentation, our cloudant
connector, our CLI, and more. We're happy to have her on our team and look forward to see what she accomplishes in the future.
DefaultCrudRepository
against loopback-connector-mongodb
. This suite will help us to catch database-specific problems that went undiscovered so far. See PR#3097.cloudant
connector test setup so that both juggler versions 3.x and 4.x are triggered. So far connectors mongodb
, postgresql
, kv-redis
, cloudant
run the shared tests. See PR#206.lb remote-method
and lb middleware
. See PR#410.@loopback/openapi-v3-types
package. See PR#3220.We have finished the migration from GreenKeeper to RenovateBot and added documentation for LoopBack developers describing how to work with RenovateBot's pull requests. Learn more in the new section Renovate bot in our documentation for developers.
We have upgraded our eslint-related infrastructure to eslint version 6 and added few more rules to the default eslint config to catch even more programming errors:
As you might be aware, the loopback.io website has a brand new look. We'd like to rebuild the "Who's using LoopBack" section and showcase our users and their use cases. If you would like to be a part of it, see the details in this GitHub issue.
If you're interested in what we're working on next, you can check out the July milestone.
LoopBack's future success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Please join us and help the project by:
We've refactored the authentication component to be more extensible and easier to use.
Now you can secure your endpoints with both passport-based and LoopBack native
authentication strategies that implement the interface AuthenticationStrategy.
The new design greatly simplifies the effort of application developers and extension developers since they now only need to focus on binding strategies to the application without having to understand/modify the strategy resolver or the action provider.
The core of the authentication component is available in @loopback/authentication version 2.x
, and the passport-based capabilities are now available in @loopback/authentication-passport.
Here is a high level overview of the authentication component.
Detailed documentation about the design and usage of @loopback/authentication@2.x
can be found here.
As an application developer, you only need 3 steps to secure your endpoints:
@authenticate(strategyName, options?)
decoratorAs an extension developer, you can contribute a LoopBack native
authentication strategy by following the steps in Creating a Custom Authentication Strategy, or a passport-based
authentication strategy by following the steps in Wrapping a Passport-based Strategy with the Passport Strategy Adapter.
A tutorial and reference implementation on how to add JWT authentication to a LoopBack 4 application using @loopback/authentication@2.x
can be found here. It involves an updated version of the example shopping cart application.
As you might be aware, our loopback.io web site has a brand new look. We're rebuilding the "Who's using LoopBack"
section to showcase our users and the use cases. If you would like to be a part of it, see the details in this GitHub issue.
LoopBack's future success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Please join us and help the project by:
We already have some APIs that allow users to customize their characters. However, a user should not get access to characters that belong to other users. With that in mind, we will add user authentication and role-based access control to this project.
You can check here for this episode's code.
In this series, I’m going to help you learn LoopBack 4 and how to use it to easily build your own API and web project. We’ll create a new project I’ve been working on: an online web text-based adventure game. In this game, you can create your own account to build characters, fight monsters and find treasures. You will be able to control your character to take a variety of actions: attacking enemies, casting spells, and getting loot. This game also allows multiple players to log in and play with their friends.
In the last episode, we created customized APIs to manage weapon
, armor
, and skill
for character
.
Here are the previous episodes:
LoopBack 4 provides us a built-in authentication package. This package includes an authentication system as the skeleton to verify the identity of a request. It invokes an authentication strategy provided by the developer to process the authentication information in the request and to then return the corresponding user profile.
In this episode, I will combine LoopBack authentication package with my self-defined authorization. This diagram shows the basic structure:
The one in the middle is the @loopback/authentication
package. It has three main components:
Providers:
@authenticate
decorator is used.Services: all services in this package are interfaces. You can create your own services as well.
Decorators: @authenticate
. Annotate the APIs that need authentication with this decorator.
The one in the bottom left is our self-defined authorization. It has three components:
Providers:
Strategies: this is where we add our own authentication strategies.
Services:
Interceptors:
Here is a diagram to show you what will happen after an API call.
application.ts
, sequence.ts
and controller
In order to use the all of above in our project, we have three more steps to complete:
application.ts
. application.ts
is like the main function of LoopBack project.sequence.ts
. A sequence contains a list of actions that is performed for each request.@authenticate
decorator above your APIs.You can check this tutorial or this shopping example for more information of LoopBack 4 Authentication package.
@loopback/authentication
Simply run npm install --save @loopback/authentication@latest
in your project root.
Reminder: We are using @loopback/authentication@2.1.0
in this project. If you want to use other versions, you may need to change you code accordingly. You may also need to run npm outdated
in your project root to see if other LoopBack packages need update.
In previous episodes, we used UUIDs as our character
IDs. But UUIDs are 36 digits string IDs. We can not let user use UUIDs to login. So we will use email instead of UUID.
To keep this project as simple as possible, a user can only own one character. So user and character are basically the same thing. character
model holds all user information.
In src/controllers/character.models
, remove id and add email and password properties.
@property({
type: 'string',
id: true,
required: true,
})
email?: string;
@property({
type: 'string',
required: true,
})
password: string;
Besides, we need to add user permission to character
model.
import {PermissionKey} from '../authorization';
@property.array(String)
permissions: PermissionKey[];
permissions
is an array of PermissionKey
s. We will create PermissionKey
later.
First, let's create a folder 'authorization' in src
to hold everything in this episode. This will be our self-defined authorization package.
I will show you how to create everything step by step. You can also check here for my authorization
folder.
Let's create permissions for users. An API may have one or more required permissions. Users need to have all of the required permissions to access that API.
Create permission-key.ts
in src/authorization
.
export const enum PermissionKey {
// For accessing own (logged in user) profile
ViewOwnUser = 'ViewOwnUser',
// For creating a user
CreateUser = 'CreateUser',
// For updating own (logged in user) profile
UpdateOwnUser = 'UpdateOwnUser',
// For deleting a user
DeleteOwnUser = 'DeleteOwnUser',
//admin
// For updating other users profile
UpdateAnyUser = 'UpdateAnyUser',
// For accessing other users profile.
ViewAnyUser = 'ViewAnyUser',
// For deleting a user
DeleteAnyUser = 'DeleteAnyUser',
}
This file holds all permissions. ViewOwnUser
, CreateUser
, UpdateOwnUser
, DeleteOwnUser
are for regular users. UpdateAnyUser
, ViewAnyUser
, DeleteAnyUser
are for admins only.
To make it easier to import, we will put all of useful interfaces, types, and schemas together.
Create types.ts
in src/authorization
.
import {PermissionKey} from './permission-key';
export interface UserPermissionsFn {
(
userPermissions: PermissionKey[],
requiredPermissions: RequiredPermissions,
): boolean;
}
export interface MyUserProfile {
id: string;
email: string;
name: string;
permissions: PermissionKey[];
}
export interface RequiredPermissions {
required: PermissionKey[];
}
export const UserProfileSchema = {
type: 'object',
required: ['email', 'password', 'name'],
properties: {
email: {
type: 'string',
format: 'email',
},
password: {
type: 'string',
minLength: 8,
},
name: {type: 'string'},
},
};
export const UserRequestBody = {
description: 'The input of create user function',
required: true,
content: {
'application/json': {schema: UserProfileSchema},
},
};
export interface Credential {
email: string;
password: string;
permissions: PermissionKey[];
}
export const CredentialsSchema = {
type: 'object',
required: ['email', 'password'],
properties: {
email: {
type: 'string',
format: 'email',
},
password: {
type: 'string',
minLength: 8,
},
},
};
export const CredentialsRequestBody = {
description: 'The input of login function',
required: true,
content: {
'application/json': {schema: CredentialsSchema},
},
};
MyUserProfile
is the format of our user profile. It is the information needed to perform authentication and authorization.
UserProfileSchema
and CredentialsSchema
are the formats of request input. We use them to validate request input in controller
.
Create keys.ts
in src/authorization
. MyAuthBindings
is the self-defined component that we need to bind to application.ts
. TokenServiceConstants
is the value we will use later in token service.
import {BindingKey} from '@loopback/context';
import {UserPermissionsFn} from './types';
import {TokenService} from '@loopback/authentication';
/**
* Binding keys used by this component.
*/
export namespace MyAuthBindings {
export const USER_PERMISSIONS = BindingKey.create<UserPermissionsFn>(
'userAuthorization.actions.userPermissions',
);
export const TOKEN_SERVICE = BindingKey.create<TokenService>(
'services.authentication.jwt.tokenservice',
);
}
export namespace TokenServiceConstants {
export const TOKEN_SECRET_VALUE = 'myjwts3cr3t';
export const TOKEN_EXPIRES_IN_VALUE = '600';
}
The LoopBack authorization package gives us three providers for authorization: strategies, action, and metadata. We need to customize our own provider for users permissions.
Create folder providers
in src
, then inside providers
, create user-permissions.provider.ts
.
import {Provider} from '@loopback/context';
import {PermissionKey} from '../permission-key';
import {UserPermissionsFn, RequiredPermissions} from '../types';
import {intersection} from 'lodash';
export class UserPermissionsProvider implements Provider<UserPermissionsFn> {
constructor() {}
value(): UserPermissionsFn {
return (userPermissions, requiredPermissions) =>
this.action(userPermissions, requiredPermissions);
}
action(
userPermissions: PermissionKey[],
requiredPermissions: RequiredPermissions,
): boolean {
return intersection(userPermissions, requiredPermissions.required).length
=== requiredPermissions.required.length;
}
}
It will compare a user's permissions and required permissions, and allow the user to get access if and only if this user has all of the required permissions.
The AuthenticationStrategyProvider
can find a registered strategy by its name. We will create our own custom authentication strategy and then specify its name in the @authenticate
decorator. It will based on the JSON Web Token.
First, run npm install jsonwebtoken --save
in your project root to install the JWT package.
Create a folder strategies
in src/authorization
. Then inside strategies
, create a file named JWT.strategy.ts
. This is our custom authentication strategy.
import {Request, HttpErrors} from '@loopback/rest';
import {inject} from '@loopback/core';
import {AuthenticationStrategy,
AuthenticationMetadata,
AuthenticationBindings,
TokenService,
} from '@loopback/authentication';
import {MyUserProfile,
UserPermissionsFn,
RequiredPermissions,} from '../types';
import {MyAuthBindings,} from '../keys';
export class JWTStrategy implements AuthenticationStrategy{
name: string = 'jwt';
constructor(
@inject(AuthenticationBindings.METADATA)
public metadata: AuthenticationMetadata,
@inject(MyAuthBindings.USER_PERMISSIONS)
protected checkPermissons: UserPermissionsFn,
@inject(MyAuthBindings.TOKEN_SERVICE)
protected tokenService: TokenService,
) {}
async authenticate(request: Request): Promise<MyUserProfile | undefined> {
const token: string = this.extractCredentials(request);
try{
const user: MyUserProfile = await this.tokenService.verifyToken(token) as MyUserProfile;
return user;
} catch (err) {
Object.assign(err, {code: 'INVALID_ACCESS_TOKEN', statusCode: 401,});
throw err;
}
}
extractCredentials(request: Request): string {
if (!request.headers.authorization) {
throw new HttpErrors.Unauthorized(`Authorization header not found.`);
}
const authHeaderValue = request.headers.authorization;
if (!authHeaderValue.startsWith('Bearer')) {
throw new HttpErrors.Unauthorized(
`Authorization header is not of type 'Bearer'.`,
);
}
const parts = authHeaderValue.split(' ');
if (parts.length !== 2)
throw new HttpErrors.Unauthorized(
`Authorization header value has too many parts. It must follow the pattern: 'Bearer xx.yy.zz' where xx.yy.zz is a valid JWT token.`,
);
const token = parts[1];
return token;
}
}
You can even use multiple strategies in one project, if needed.
Interceptor is a middle layer comes after authentication. After the authentication strategy verified user's access token, interceptor will verify use's permission.
Run lb4 interceptor
in your project root.
? Interceptor name: authorize
? Is it a global interceptor? Yes
? Global interceptors are sorted by the order of an array of group names bound to ContextBindings.GLOBAL_INTERCEPTOR_ORDERED_GROUPS. See https://loopback.io/doc/en/lb4/Interceptors.html#order-of-invocation-for-interceptors.
Group name for the global interceptor: ('')
create src/interceptors/authorize.interceptor.ts
update src/interceptors/index.ts
Interceptor authorize was created in src/interceptors/
Then change src/interceptors/authorize.interceptor.ts
to this:
import {
inject,
globalInterceptor,
Interceptor,
InvocationContext,
InvocationResult,
Provider,
ValueOrPromise,
} from '@loopback/context';
import {Getter} from '@loopback/core';
import {HttpErrors} from '@loopback/rest';
import {MyUserProfile,
MyAuthBindings,
UserPermissionsFn,
RequiredPermissions,} from '../authorization';
import {AuthenticationMetadata,AuthenticationBindings} from '@loopback/authentication';
/**
* This class will be bound to the application as an `Interceptor` during
* `boot`
*/
@globalInterceptor('', {tags: {name: 'authorize'}})
export class AuthorizationInterceptor implements Provider<Interceptor> {
constructor(
@inject(AuthenticationBindings.METADATA)
public metadata: AuthenticationMetadata,
@inject(MyAuthBindings.USER_PERMISSIONS)
protected checkPermissons: UserPermissionsFn,
@inject.getter(AuthenticationBindings.CURRENT_USER)
public getCurrentUser: Getter<MyUserProfile>,
) {}
/**
* This method is used by LoopBack context to produce an interceptor function
* for the binding.
*
* @returns An interceptor function
*/
value() {
return this.intercept.bind(this);
}
/**
* The logic to intercept an invocation
* @param invocationCtx - Invocation context
* @param next - A function to invoke next interceptor or the target method
*/
async intercept(
invocationCtx: InvocationContext,
next: () => ValueOrPromise<InvocationResult>,
) {
if (!this.metadata) return await next();
const result = await next();
const requiredPermissions = this.metadata.options as RequiredPermissions;
const user = await this.getCurrentUser();
if(!this.checkPermissons(user.permissions, requiredPermissions)){
throw new HttpErrors.Forbidden('INVALID_ACCESS_PERMISSION');
}
return result;
}
}
A global interceptor will be automatically applied to all methods in controllers.
Create a folder services
in src/authorization
, then inside services
, create a file named JWT.service.ts
. This is a service that generates and verifies JWT tokens, and will be used by JWTStrategy.
import {inject} from '@loopback/context';
import {HttpErrors} from '@loopback/rest';
import {promisify} from 'util';
import {TokenService} from '@loopback/authentication';
import {TokenServiceConstants} from '../keys';
import {MyUserProfile, Credential} from '../types';
import {repository} from '@loopback/repository';
import {CharacterRepository} from '../../repositories';
import * as _ from 'lodash';
import {toJSON} from '@loopback/testlab';
const jwt = require('jsonwebtoken');
const signAsync = promisify(jwt.sign);
const verifyAsync = promisify(jwt.verify);
export class JWTService implements TokenService {
constructor(
@repository(CharacterRepository)
public characterRepository: CharacterRepository,
) {}
async verifyToken(token: string): Promise<MyUserProfile> {
if (!token) {
throw new HttpErrors.Unauthorized(
`Error verifying token : 'token' is null`,
);
}
const decryptedToken = await verifyAsync(token, TokenServiceConstants.TOKEN_SECRET_VALUE);
let userProfile = _.pick(decryptedToken, ['id', 'email', 'name', `permissions`]);
return userProfile;
}
async generateToken(userProfile: MyUserProfile): Promise<string> {
const token = await signAsync(userProfile, TokenServiceConstants.TOKEN_SECRET_VALUE, {
expiresIn: TokenServiceConstants.TOKEN_EXPIRES_IN_VALUE,
});
return token;
}
async getToken(credential: Credential): Promise<string> {
const foundUser = await this.characterRepository.findOne({
where: {email: credential.email},
});
if (!foundUser) {
throw new HttpErrors['NotFound'](
`User with email ${credential.email} not found.`,
);
}
if (credential.password != foundUser.password) {
throw new HttpErrors.Unauthorized('The credentials are not correct.');
}
const currentUser: MyUserProfile = _.pick(toJSON(foundUser), ['email', 'name', 'permissions']) as MyUserProfile;
const token = await this.generateToken(currentUser);
return token;
}
}
You can also create your own authentication services, like Hash Password service in the shopping example.
application.ts
Open src/application.ts
, and add the following imports.
import {asGlobalInterceptor} from '@loopback/context';
import {MyAuthBindings,
JWTService,
JWTStrategy,
UserPermissionsProvider
} from './authorization';
import {AuthorizationInterceptor} from './interceptors';
import {AuthenticationComponent,
registerAuthenticationStrategy,
} from '@loopback/authentication';
Then, add the following lines in the constructor.
constructor(options: ApplicationConfig = {}) {
super(options);
//add
// Bind authentication component related elements
this.component(AuthenticationComponent);
// Bind JWT & permission authentication strategy related elements
registerAuthenticationStrategy(this, JWTStrategy);
this.bind(MyAuthBindings.TOKEN_SERVICE).toClass(JWTService);
this.bind(MyAuthBindings.USER_PERMISSIONS).toProvider(UserPermissionsProvider);
If you have more authentication strategies, add them in this way:
registerAuthenticationStrategy(this, NewStrategy);
sequence.ts
In src/sequence.ts
, add the following imports.
import {
AuthenticationBindings,
AuthenticateFn,
} from '@loopback/authentication';
Then add those lines in the handle
function.
async handle(context: RequestContext) {
try {
const {request, response} = context;
const route = this.findRoute(request);
const args = await this.parseParams(request, route);
//add authentication actions
await this.authenticateRequest(request);
const result = await this.invoke(route, args);
this.send(response, result);
} catch (err) {
if (
err.code === 'AUTHENTICATION_STRATEGY_NOT_FOUND' ||
err.code === 'USER_PROFILE_NOT_FOUND'
) {
Object.assign(err, {statusCode: 401 /* Unauthorized */});
}
this.reject(context, err);
return;
}
}
This will check for authentication for every request.
Our Authentication and Authorization components are ready to use. Now we can apply their decorators to our REST API endpoints.
Open src/controllers/character.controller.ts
, add the following imports.
import {
MyUserProfile,
Credential,
MyAuthBindings,
PermissionKey,
CredentialsRequestBody,
UserRequestBody,
UserProfileSchema,
JWTService,
} from '../authorization';
import {authenticate,
TokenService,
AuthenticationBindings,
} from '@loopback/authentication';
Inject TOKEN_SERVICE
and CURRENT_USER
in the constructor.
@inject(MyAuthBindings.TOKEN_SERVICE)
public jwtService: JWTService,
@inject.getter(AuthenticationBindings.CURRENT_USER)
public getCurrentUser: Getter<MyUserProfile>,
Then let's make some changes to the @post /characters
API.
@post('/characters', {
responses: {
'200': {
description: 'Character model instance',
content: {'application/json': {schema: {'x-ts-type': Character}}},
},
},
})
async create(
@requestBody(UserRequestBody) character: Character
): Promise<Character> {
character.permissions = [PermissionKey.ViewOwnUser,
PermissionKey.CreateUser,
PermissionKey.UpdateOwnUser,
PermissionKey.DeleteOwnUser];
if (await this.characterRepository.exists(character.email)){
throw new HttpErrors.BadRequest(`This email already exists`);
}
else {
const savedCharacter = await this.characterRepository.create(character);
delete savedCharacter.password;
return savedCharacter;
}
}
Put UserRequestBody
in @requestBody
decorator to specify the format of request body. That is how we validate the format of email and password fields.
Because this API is used to create a regular character, we will assign ViewOwnUser
, CreateUser
, UpdateOwnUser
, and DeleteOwnUser
permissions to the new character.
We also need to create an API for login.
@post('/characters/login', {
responses: {
'200': {
description: 'Token',
content: {},
},
},
})
async login(
@requestBody(CredentialsRequestBody) credential: Credential,
): Promise<{token: string}> {
const token = await this.jwtService.getToken(credential);
return {token};
}
This API will use JWTService
to verify user email and password, and then generate a JWT based on necessary credential information, like email, password and permissions.
The next API we need is @get /characters/me
. It will show the user current logged in.
@get('/characters/me', {
responses: {
'200': {
description: 'The current user profile',
content: {
'application/json': {
schema: UserProfileSchema,
},
},
},
},
})
@authenticate('jwt', {"required": [PermissionKey.ViewOwnUser]})
async printCurrentUser(
): Promise<MyUserProfile> {
return await this.getCurrentUser();
}
We will authenticate this API with @authenticate('jwt', {"required": [PermissionKey.ViewOwnUser]})
. The first parameter jwt
specify which authentication strategy you want to use for this API. If you have more than one strategy, make your choice here. The second parameter is an object which has one field required
(array of PermissionKey
). It specifies which permissions are required to access this API. In this case, the only required permission is ViewOwnUser
. Because this API only shows current logged-in user information. You can customize permissions based on your APIs.
To get current logged-in user information, simply call this.getCurrentUser()
.
The above three APIs show you most of the use cases. You should now have enough knowledge on how to apply LoopBack 4 authentication to your APIs.
Let me show you one more example before we're done.
Let's create another controller for admins. Admins should have privilege to view, update, and delete any user.
Here is how we create an admin.
@post('/admin', {
responses: {
'200': {
description: 'create admin',
content: {'application/json': {schema: {'x-ts-type': Character}}},
},
},
})
async create(
@param.query.string('admin_code') admin_code: string,
@requestBody() character: Character,
): Promise<Character> {
if(admin_code != '901029'){
throw new HttpErrors.Forbidden('WRONG_ADMIN_CODE');
}
character.permissions = [PermissionKey.ViewOwnUser,
PermissionKey.CreateUser,
PermissionKey.UpdateOwnUser,
PermissionKey.DeleteOwnUser,
PermissionKey.UpdateAnyUser,
PermissionKey.ViewAnyUser,
PermissionKey.DeleteAnyUser];
if (await this.characterRepository.exists(character.email)){
throw new HttpErrors.BadRequest(`This email already exists`);
}
else {
const savedCharacter = await this.characterRepository.create(character);
delete savedCharacter.password;
return savedCharacter;
}
}
It is very similar to the @post /characters
API. The difference is it requires an admin_code
to create an admin with three more permissions: UpdateAnyUser
, ViewAnyUser
, and DeleteAnyUser
.
This is an API to show the information of all users that match the filter criteria.
@get('/admin/characters', {
responses: {
'200': {
description: 'Array of Character model instances',
content: {
'application/json': {
schema: {type: 'array', items: {'x-ts-type': Character}},
},
},
},
},
})
@authenticate('jwt', {"required": [PermissionKey.ViewAnyUser]})
async find(
@param.query.object('filter', getFilterSchemaFor(Character)) filter?: Filter,
): Promise<Character[]> {
return await this.characterRepository.find(filter);
}
As you can see, this requires ViewAnyUser
permission.
You can check my controllers here
In this episode, we covered how to combine your self-defined authorization strategies and services with @loopback/authentication
and how to apply it to your API.
You can always design your own strategies and services based on your project needs. For example, you may want to have a password hashing service, so that you don't directly save a user's raw password in the database. Here is an example of how to implement a password hashing service.
Next episode, we will deploy this project to cloud. In the meantime, you can learn more about LoopBack in past blogs.
]]>Following the announcement of LoopBack 4 GA in October, LoopBack 3 entered Active Long Term Support (LTS). In March, we announced that LoopBack 3 will receive extended LTS until December 2019. We made this choice to provide LoopBack 3 users more time to move to LoopBack 4 and for us to improve the migration experience. In order to incrementally migrate from LoopBack 3 to LoopBack 4, we have since introduced a way to mount your LoopBack 3 applications in a LoopBack 4 project.
Miroslav investigated different approaches for migration which you can see in the Migration epic. He settled on mounting the LoopBack 3 application in a LoopBack 4 project as part of incremental migration (see the PoC here). In this approach, the entire LoopBack 3 application is mounted, including its runtime dependencies. The LoopBack 3 Swagger spec is also converted to LoopBack 4 OpenAPI v3 and a unified spec is created.
If you are a current LoopBack 3 user who wants to migrate to LoopBack 4, learn all the steps needed to mount your application in this Migrating from LoopBack 3 doc.
LoopBack's future success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Please join us and help the project by:
As the weather starts to warm up for summer, the LoopBack team has turned up the heat on the milestone tasks we planned for the month of May and beyond. We worked on areas such as:
loopback.io
.These are just the tip of the iceberg. Read on to find out more on what the LoopBack team accomplished this month.
Interceptors are reusable functions to provide aspect-oriented logic around method invocations. There are many use cases for interceptors, such as:
See PR#2687 for more details.
To allow bound items in the context to be configured, we introduced some conventions and corresponding APIs to make it simple and consistent in PR#2259.
We treat configurations for bound items in the context as dependencies, which can be resolved and injected in the same way of other forms of dependencies. For example, the RestServer
can be configured with RestServerConfig
.
We introduced a new example that illustrates mounting an existing LoopBack 3 application in a LoopBack 4 project. We took the CoffeeShop
application from loopback-getting-started and mounted it in a new LoopBack 4 project. The endpoints, including the OpenAPI spec, have also been mounted in the project. You can check out the example by running lb4 example lb3-application
.
Controller methods can now reference model definitions through OpenAPI spec. This replaces the need to use x-ts-type
extension to reference model schema. In addition, if an operation defines the model, another operation can reference that model without defining it.
For example, in the following code snippet, since the Todo
model definition is provided by @get('/todos')
, @get('/todos/{id')
can now also reference it without defining it.
class MyController {
@get('/todos', {
responses: {
'200': {
description: 'Array of Category model instances',
content: {
'application/json': {
schema: {
$ref: '#/definitions/Todo',
definitions: {
Todo: {
title: 'Todo',
properties: {
title: {type: 'string'},
},
},
},
},
},
},
},
},
})
async find(): Promise<object[]> {
/* … */
}
@get('/todos/{id}', {
responses: {
'200': {
description: 'Todo model instance',
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/Todo',
},
},
},
},
},
},
})
async findById(): Promise<object> {
/* … */
}
}
To make model references even easier to use, we have also introduced a new helper getModelSchemaRef
which builds a $ref
entry with accompanied schema in definitions
from the given model instance. See PR#2971.
class MyController {
@get('/todos', {
responses: {
'200': {
description: 'Array of Category model instances',
content: {
'application/json': {
schema: getModelSchemaRef(Todo),
},
},
},
},
})
async find(): Promise<object[]> {
/* … */
}
@get('/todos/{id}', {
responses: {
'200': {
description: 'Todo model instance',
content: {
'application/json': {
schema: getModelSchemaRef(Todo),
},
},
},
},
})
async findById(): Promise<object> {
/* … */
}
}
In the near future, we would like to leverage this new mechanism to emit different model schema for different API endpoints:
find
and findById
endpoints should include navigational properties for related models.create
endpoint should exclude id
and any other database-generated properties (e.g. _rev
in CouchDB/Cloudant).updateById
and updateAll
endpoints should mark all model properties as optional.PR#2843 improved the way how our CLI tool serialized model settings to TypeScript code.
Before this change, model settings object was converted to JSON, which produced non-idiomatic TypeScript code. For example:
@model({ settings: {"mongodb": {"collection": "clients"}}})
export class MyModel extends Entity {
// ...
}
Now we convert the object into TypeScript object definition. See below for an example:
@model({
settings: {
mongodb: {collection: 'clients'},
}
})
export class MyModel extends Entity {
// ...
}
You can set a model property's mongodb
property definition dataType
to "ObjectID" to enforce ObjectID coercion irrespective of the strictObjectIDCoercion
setting.
In the following example, the id
and xid
will be coerced to ObjectID
even if strictObjectIDCoercion
is set to true.
const User = ds.createModel(
'user',
{
id: {type: String, id: true, mongodb: {dataType: 'ObjectID'}},
xid: {type: String, mongodb: {dataType: 'ObjectID'}}
},
{strictObjectIDCoercion: true}
);
For more details, see PR#517.
This month we implemented a PoC PR to illustrate plugging in the passport strategy in the new published @loopback/autthentication
module. The PR proves the new authentication action is able to verify a request with both passport based and non passport based strategy. Therefore we just deprecated the old passport adapter in story 2467, and focus on the new adapter creation in story 2311.
The initial proposal for the new adapter is wrapping each configured strategy instance so that it fits the AuthenticationStrategy
interface. Raymond suggested exploring another approach which applies a wrapper to the passport
module itself. And since the team is discussing the work flow of incrementally adding a new feature, Miroslav suggested moving the new passport adapter to a standalone experimental module along with the comparison between two proposals. Eventually the adapter will be a component that can be plugged into @loopback/authentication
.
We've introduced experimental features to achieve a balance between the high code quality and the development speed.
At first we tried keeping the lab packages in a mirror repository loopback-labs
for the purpose of isolation, then graduate the features into loopback-next
when they are production ready. During the spike, we realized a better way to isolate each feature: create and release each feature from its own branch, so that features are independent from each other and have their own life cycle. The releasing process is also simpler since it only targets on one package. With this in mind, and considering the synchronization effort between two repositories, we decided to keep lab packages stay in loopback-next
.
The work flow of adding an experimental package is documented in https://github.com/strongloop/loopback-next/blob/labs/base/LABS.md. It explains how to create your experimental feature branch, incrementally improve the code and graduate the package.
We took some time to rework outdated sections of our documentation pages in May. First of all, we updated docs on how to create stub services for controller unit tests. At the same time, we updated the status of auto-generated smoke test section and top-down application building. See PR#2879 for more details. Moreover, our hasOne relation page also had some typos and errors and we were able to address them in PR#2959.
In PR#2944, we revamped the Sequences documentation page with information on what happens behind the scenes for some of the sequence actions like invoke
and send
. We also added details on how to retrieve query string parameters.
We added a new Components page to explain the shape and concept of components for extensibility as well as how they contribute artifacts to a LoopBack 4 application. For more details, check out PR#2702 and PR#2361.
In PR#2834, we introduced @loopback/tsdocs
package which provides API Docs for the rest of the LB4 packages in our loopback-next
monorepo using Microsoft's api-extractor
and api-documenter
tools. It also integrates the generated API Docs for consumption in loopback.io
. You can see them live here.
We added a new greeting-app that shows how to compose an application from component and controllers, interceptors, and observers. The application is built on top of example-greeter-extension and can greet users in different languages. It has a caching interceptor to handle multiple requests for the same payload (name and language) within a specific time. See PR#2941 for more details.
On the same note, we added standalone examples for @loopback/context
showing how to use the module as an Inversion of Control (IoC) and Dependency Injection (DI) container. We covered a wide array of usages for the utilities provided by the module such as context chaining, binding types, and customizing injection decorators/resolvers.
We created the dedicated web site for LoopBack 4 last year. Now that LoopBack 4 has become the active release, we are moving the LoopBack 4 pages back to loopback.io web site.
In the meantime, we're actively attempting to rebuild the “Who’s using LoopBack” section to showcase our users and the use cases. If you would like to be a part of it, please sign up here!
PR#2856 fixed REST API Explorer to correctly handle both basePath
configuration specified at LoopBack application/server level and mountPath
provided by Express when the LB app is mounted on an Express application.
PR#2823 improved the OpenAPI spec emitted for LoopBack 3 models when the entire LB3 application is mounted in LB4 project. In particular, the schema for request bodies of "create" requests now excludes id
(primary key) properties when the model is configured with forceId: true
(which is the default).
Keeping dependencies up-to-date is time consuming, especially in monorepos. Originally, we were used GreenKeeper to automate the task. Unfortunately, GreenKeeper does not support the combination of lerna monorepos with package-locks enabled (see greenkeeper#1080).
In May, we migrated our Shopping example app to RenovateBot. The migration went well, and we are now migrating the main loopback-next monorepo too.
PR#3013 removed implicit dependency of @loopback/testlab
on @types/mocha
. This dependency was preventing LoopBack projects to use Jest test framework instead of Mocha. Now that the fix was landed and published to npm registry, @loopback/testlab
should work with any test framework that provides it
and it.skip
APIs.
PR#2996 makes sure we stay current with TypeScript and upgrades our project to use version 3.5.1
.
Node.js 6 went end-of-life at the end of April. We are incrementally removing it from our CI build configurations and updating our package.json
files to bump up the minimum supported version to Node.js 8.
When we introduced version 4 of loopback-datasource-juggler back in 2018, we did not update connector repositories to run tests against this new juggler version too. As a result, we were encountering failed builds when back-porting pull requests from juggler 4.x to 3.x. We looked into different ways how to address this problem and decided to go with a solution where a single connector run shared tests from multiple loopback-datasource-juggler versions. Some of the connectors have been already updated, we will be updating the rest in the next months.
Additional changes:
generator-loopback
: The version of the Node.js engine is updated to >= 8 because Node.js 6 entered EOF in April 2019.generator-loopback
: We fixed two regression bugs to honour the name argument of command lb swagger
and lb boot-script
.loopback-passport-component
: We landed a community PR to support user ldap profile configuration with group search for Microsoft active directory, you can check the details in PR #267.loopback-connector-cloudant
: The CI failures are fixed after upgrading the dependency version to loopback-connector-couchdb2@1.4.0
.LoopBack's future success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Please join us and help the project by:
In this series, I’m going to help you learn LoopBack 4 and how to use it to easily build your own API and web project. We’ll create a new project I’ve been working on: an online web text-based adventure game. In this game, you can create your own account to build characters, fight monsters and find treasures. You will be able to control your character to take a variety of actions: attacking enemies, casting spells, and getting loot. This game also allows multiple players to log in and play with their friends.
In the last episode, we used a third-party library to generate UUID and built relations between character
, weapon
, armor
, and skill
.
Here are the previous episodes:
We already have some simple APIs in our project. They are all default CRUD (Create, Read, Update, and Delete) APIs that auto-generated by LoopBack 4. In this episode, we will create our own APIs to achieve the following functions for character updating:
defence
and attack
accordingly.defence
and attack
.currentExp
, nextLevelExp
, level
, maxHealth
, currentHealth
, maxMana
, currentMana
, attack
, and defence
.weapon
, armor
, and skill
information.First, let's create a controller for updating your character. Run lb4 controller
in your project root.
wenbo:firstgame wenbo$ lb4 controller
? Controller class name: UpdateCharacter
? What kind of controller would you like to generate? REST Controller with CRUD functions
? What is the name of the model to use with this CRUD repository? Character
? What is the name of your CRUD repository? CharacterRepository
? What is the type of your ID? string
? What is the base HTTP path name of the CRUD operations? /updatecharacter
create src/controllers/update-character.controller.ts
update src/controllers/index.ts
Controller UpdateCharacter was created in src/controllers/
Open /src/controllers/update-character.controller.ts
. Add the following imports because this controller is
associated with Armor
, Weapon
, skill
as well.
import {Armor, Weapon, Skill} from '../models';
import {WeaponRepository, ArmorRepository, SkillRepository } from '../repositories';
Then add the following lines into constructor:
constructor(
@repository(CharacterRepository)
public characterRepository : CharacterRepository,
//add following lines
@repository(WeaponRepository)
public weaponRepository : WeaponRepository,
@repository(ArmorRepository)
public armorRepository : ArmorRepository,
@repository(SkillRepository)
public skillRepository : SkillRepository,
) {}
This will connect this controller with Armor
, Weapon
, and skill
. You can now delete all those default APIs since we don't need them anymore.
The first API we need is @patch '/updatecharacter/{id}/weapon'
. In this game, a character can only have one weapon. With that in mind, this API's job is to equip characters with a weapon and unequip the old weapon if there is one.
Here is code for this API:
@patch('/updatecharacter/{id}/weapon', {
responses: {
'200': {
description: 'update weapon',
content: {'application/json': {schema: Weapon}},
},
},
})
async updateWeapon(
@param.path.string('id') id: string,
@requestBody() weapon: Weapon,
): Promise<Weapon> {
//equip new weapon
let char: Character = await this.characterRepository.findById(id);
char.attack! += weapon.attack;
char.defence! += weapon.defence;
//unequip old weapon
let filter: Filter = {where:{"characterId":id}};
if((await this.weaponRepository.find(filter))[0] != undefined){
let oldWeapon: Weapon = await this.characterRepository.weapon(id).get();
char.attack! -= oldWeapon.attack;
char.defence! -= oldWeapon.defence;
await this.characterRepository.weapon(id).delete();
}
await this.characterRepository.updateById(id, char);
return await this.characterRepository.weapon(id).create(weapon);
}
Let's go over it line by line.
This is the function signature. It means this API expects to get a character ID from URL and weapon entity from body.
async updateWeapon(
@param.path.string('id') id: string,
@requestBody() weapon: Weapon,
): Promise<Weapon> {
...
The following lines will find the character entity from our database. Then we will update this character's attack
and defence
. The !
after attack
and defence
tells the compiler we guarantee those variables are not undefined. Otherwise, we will get a compile error. In the weapon
model, attack
and defence
are both required, so these cannot be empty.
//equip new weapon
let char: Character = await this.characterRepository.findById(id);
char.attack! += weapon.attack;
char.defence! += weapon.defence;
This block will check if this character already has a weapon. If so, it will update the character's attack
and defence
and remove the old weapon from database.
//unequip old weapon
let filter: Filter = {where:{"characterId":id}};
if((await this.weaponRepository.find(filter))[0] != undefined){
let oldWeapon: Weapon = await this.characterRepository.weapon(id).get();
char.attack! -= oldWeapon.attack;
char.defence! -= oldWeapon.defence;
await this.characterRepository.weapon(id).delete();
}
Those two lines will update the update character information in our database and put the new weapon into it.
await this.characterRepository.updateById(id, char);
return await this.characterRepository.weapon(id).create(weapon);
We need to handle armor
exactly the same. But skill
is a little bit different, because in this game skill
will not influence attack
and defence
. We just need to update our new skill and delete the old skill.
@patch('/updatecharacter/{id}/skill', {
responses: {
'200': {
description: 'update skill',
content: {'application/json': {schema: Skill}},
},
},
})
async updateSkill(
@param.path.string('id') id: string,
@requestBody() skill: Skill,
): Promise<Skill> {
await this.characterRepository.skill(id).delete();
return await this.characterRepository.skill(id).create(skill);
}
When we delete a character, we also need to delete its weapon
, armor
, and skill
. To do this, open /src/controllers/character.controller.ts
and add the following lines in del '/characters/{id}
API.
@del('/characters/{id}', {
responses: {
'204': {
description: 'Character DELETE success',
},
},
})
async deleteById(
@param.path.string('id') id: string
): Promise<void> {
//delete weapon, armor, and skill
await this.characterRepository.weapon(id).delete();
await this.characterRepository.armor(id).delete();
await this.characterRepository.skill(id).delete();
///
await this.characterRepository.deleteById(id);
}
Unequipping the character is very easy.
For weapon
and armor
, simply remove them from database and update attack
and defence
.
@del('/updatecharacter/{id}/weapon', {
responses: {
'204': {
description: 'DELETE Weapon',
},
},
})
async deleteWeapon(
@param.path.string('id') id: string
): Promise<void> {
//unequip old weapon
let filter: Filter = {where:{"characterId":id}};
if((await this.weaponRepository.find(filter))[0] != undefined){
let oldWeapon: Weapon = await this.characterRepository.weapon(id).get();
let char: Character = await this.characterRepository.findById(id);
char.attack! -= oldWeapon.attack!;
char.defence! -= oldWeapon.defence!;
await this.characterRepository.weapon(id).delete();
await this.characterRepository.updateById(id, char);
}
}
For skill
, just remove it from database.
@del('/updatecharacter/{id}/skill', {
responses: {
'204': {
description: 'DELETE Skill',
},
},
})
async deleteSkill(
@param.path.string('id') id: string
): Promise<void> {
await this.characterRepository.skill(id).delete();
}
When a character has enough experience, we reward it by levelling up. In /src/controllers/update-character.controller.ts
:
@patch('/updatecharacter/{id}/levelup', {
responses: {
'200': {
description: 'level up',
content: {'application/json': {schema: Character}},
},
},
})
async levelUp(@param.path.string('id') id: string): Promise<Character> {
let char: Character = await this.characterRepository.findById(id);
let levels: number = 0;
while(char.currentExp! >= char.nextLevelExp!){
levels++;
char.currentExp! -= char.nextLevelExp!;
char.nextLevelExp! += 100;
}
char.level! += levels;
char.maxHealth! += 10 * levels;
char.currentHealth! = char.maxHealth!;
char.maxMana! += 5 * levels;
char.currentMana! = char.maxMana!;
char.attack! += 3 * levels;
char.defence! += levels;
await this.characterRepository!.updateById(id, char);
return char;
}
Let's go over this line by line.
If a character just beat a very strong enemy and gained a lot of experience, it could level up more than once. So the first thing we need to do is figure out how many times we need to level up.
let levels: number = 0;
while(char.currentExp! >= char.nextLevelExp!){
levels++;
char.currentExp! -= char.nextLevelExp!;
char.nextLevelExp! += 100;
}
Then we can update everything accordingly.
char.level! += levels;
char.maxHealth! += 10 * levels;
char.currentHealth! = char.maxHealth!;
char.maxMana! += 5 * levels;
char.currentMana! = char.maxMana!;
char.attack! += 3 * levels;
char.defence! += levels;
Lastly, we update this character in database.
await this.characterRepository!.updateById(id, char);
The last function we need to achieve is the ability to check character information.
Here is the code for this API:
@get('/updatecharacter/{id}', {
responses: {
'200': {
description: 'armor, weapon, and skill info',
content: {},
},
},
})
async findById(
@param.path.string('id') id: string,
): Promise<any[]> {
let res: any[] = ['no weapon', 'no armor', 'no skill'];
let filter: Filter = {where:{"characterId":id}};
if((await this.weaponRepository.find(filter))[0] != undefined){
res[0] = await this.characterRepository.weapon(id).get()
}
if((await this.armorRepository.find(filter))[0] != undefined){
res[1] = await this.characterRepository.armor(id).get()
}
if((await this.skillRepository.find(filter))[0] != undefined){
res[2] = await this.characterRepository.skill(id).get()
}
return res;
}
We first create an array contains three elements: 'no weapon', 'no armor', 'no skill'.
Then we will check our database. For example, if this character has a weapon, we will replace no weapon
with the weapon information. Lastly, we return the array as result.
That is all we want to achieve in this episode. If you can follow all those steps, you should be able to try those API at http://[::1]:3000
You can check here for the code of this episode.
In this episode, we covered the how to customize APIs. You can always implement your own amazing idea in your LoopBack 4 project.
In next episode, we will add user authentication and role-based access control to this project.
In the meantime, you can learn more about LoopBack in past blogs.
]]>In this series, I’m going to help you learn LoopBack 4 and how to use it to easily build your own API and web project. We’ll create a new project I’ve been working on: an online web text-based adventure game. In this game, you can create your own account to build characters, fight monsters and find treasures. You will be able to control your character to take a variety of actions: attacking enemies, casting spells, and getting loot. This game also allows multiple players to log in and play with their friends.
In the previous episode, we created a foundation for our project. Now we have some basic APIs to create, edit, and delete a character.
Here is the previous episode:
First, we will use a third-party library in our LoopBack 4 project to generate unique character IDs. Then we will create weapon
, armor
, and skill
models and build relationships between those models.
Loopback 4 supports three relations for now:
We will use HasOne
in this episode.
You can check here for the code of this episode.
In the last episode, we used a while loop to generate continuous character IDs. However, that could be disaster in a real world application. Because fetching data from database is expensive. We don't want to do that hundreds times to just find a unique character ID. On the other hand, we don't really need continuous IDs, just unique IDs to distinguish characters. So we will use a better approach to generate universally unique IDs (UUID).
MongoDB can generate unique IDs for us. You can check more details here for how to do that. However, this approach may result in changing a lot of code.
So, we are going to use a third-party library called uuid. It's very easy to use and I think it's a good idea to show you how to use a third-party library in LoopBack 4 project. Run npm install --save @types/uuid
at your project root to install it.
Then go back to src/models/character.model.ts
and change the type of id
to string. Because uuid can only generate string IDs.
@property({
//type: 'number',
type: 'string',
id: true,
})
//id?: number;
id?: string;
Go to src/controllers/character.controller.ts
. In the get /characters/{id}
API, change the type of id
to string
.
@get('/characters/{id}', {
responses: {
'200': {
description: 'Character model instance',
content: {'application/json': {schema: {'x-ts-type': Character}}},
},
},
})
async findById(
//@param.path.number('id') id: number
@param.path.string('id') id: string
): Promise<Character> {
return await this.characterRepository.findById(id);
}
Then do the same thing for patch /characters/{id}
, put /characters/{id}
, and del /characters/{id}
APIs.
The uuid can generate 36 digits string IDs. The implementation of uuid library is using some high distinction values (like DNS, local time, IP address etc) as the seed to randomly generate strings to reduce the chance of duplication. We can simply call the function uuid()
to use this library.
Remember how we generated a character ID in the last episode? We can do it in a very elegant way by using default
keyword in model
.
Open src/controllers/character.controller.ts
and remove following code from post /characters
API:
@post('/characters', {
responses: {
'200': {
description: 'Character model instance',
content: {'application/json': {schema: {'x-ts-type': Character}}},
},
},
})
async create(@requestBody() character: Character): Promise<Character> {
/**remove this
let characterId = 1;
while(await this.characterRepository.exists(characterId)){
characterId ++;
}
character.id = characterId;
*/
return await this.characterRepository.create(character);
}
Open src/models/character.model.ts
, and add the following import. This will import uuid
so we can use it in our code.
import {v4 as uuid} from 'uuid';
Add the following line to generate character ID as default
:
@property({
type: 'string',
id: true,
//add this line
default: () => uuid(),
})
id?: string;
That is how we generate UUID for character
. We will use the same way to generate UUID for other models later.
We will create weapon
, armor
, and skill
models. Each character
may have one weapon
, one armor
, and one skill
. It is a HasOne relationship.
Last episode, we built APIs for character
in the order of model, datasource, repository, and controller. Now we will do it in the same way for weapon
, armor
, and skill
. Note that we are not going to cover controller today, because I will have a whole episode focused on the work required (and there is a lot!).
First, we need to create weapon
model. It's very similar to what we did in last episode. Run lb4 model
at project root.
wenbo:firstgame wenbo$ lb4 model
? Model class name: weapon
? Please select the model base class Entity (A persisted model with an ID)
? Allow additional (free-form) properties? No
Let's add a property to Weapon
Enter an empty property name when done
? Enter the property name: id
? Property type: string
? Is id the ID property? Yes
? Is it required?: No
? Default value [leave blank for none]:
Let's add another property to Weapon
Enter an empty property name when done
? Enter the property name: name
? Property type: string
? Is it required?: Yes
? Default value [leave blank for none]:
Let's add another property to Weapon
Enter an empty property name when done
? Enter the property name: attack
? Property type: number
? Is it required?: Yes
? Default value [leave blank for none]:
Let's add another property to Weapon
Enter an empty property name when done
? Enter the property name: defence
? Property type: number
? Is it required?: Yes
? Default value [leave blank for none]:
Do the same thing for armor
and skill
.
hasOne
Model Relation in ModelNow let's add relationships to character
model to indicate that a character
has one weapon
, armor
, and skill
. You can check here for more details on model relationship. You can also take a look at the TodoList tutorial to see how it handles a relationship.
Add the following imports to the head of character.model.ts
.
import {Armor} from './armor.model';
import {Weapon} from './weapon.model';
import {Skill} from './skill.model';
Then add the following code into character.model.ts
after those auto-generated properties. That means each character
may have one weapon
, armor
, and skill
.
@hasOne(() => Armor)
armor?: Armor;
@hasOne(() => Weapon)
weapon?: Weapon;
@hasOne(() => Skill)
skill?: Skill;
Next, we need to add a relationship for weapon.model.ts
as well. Add the following imports to the head.
import {Character} from './character.model';
import {v4 as uuid} from 'uuid';
Then add this code after those auto-generated properties.
@belongsTo(() => Character)
characterId: string;
This gives weapon
another property characterId
means identifies the character this weapon belongs to. It's similar to the foreign key in a relational database.
Don't forget to generate UUID for weapon
:
@property({
type: 'string',
id: true,
//add this line
default: () => uuid(),
})
id?: string;
Do the same thing for armor.model.ts
and skill.model.ts
. Now our models are all set.
You can check my code for all models at here.
There is no need to create new datasource. We can use the MongoDB we created in last episode.
Run lb4 repository
at your project root.
? Please select the datasource MongoDatasource
? Select the model(s) you want to generate a repository Weapon
? Please select the repository base class DefaultCrudRepository (Legacy juggler bridge)
create src/repositories/weapon.repository.ts
update src/repositories/index.ts
Then create repositories for armor
and skill
in the same way.
hasOne
Model Relation in RepositoryLet's add relations for character.repository.ts
first. Add following imports:
import {HasOneRepositoryFactory, juggler, repository} from '@loopback/repository';
import {Armor, Weapon, Skill} from '../models';
import {Getter} from '@loopback/core';
import {ArmorRepository} from './armor.repository';
import {WeaponRepository} from './weapon.repository';
import {SkillRepository} from './skill.repository';
Add the following code before the constructor:
public armor: HasOneRepositoryFactory<
Armor,
typeof Character.prototype.id
>;
public weapon: HasOneRepositoryFactory<
Weapon,
typeof Character.prototype.id
>;
public skill: HasOneRepositoryFactory<
Skill,
typeof Character.prototype.id
>;
This means character
may have one weapon
, armor
, and skill
ID. Then we are able to find the correct entity by that ID.
Then change the constructor to this:
constructor(
@inject('datasources.mongo') dataSource: MongoDataSource,
@repository.getter(ArmorRepository)
protected armorRepositoryGetter: Getter<ArmorRepository>,
@repository.getter(WeaponRepository)
protected weaponRepositoryGetter: Getter<WeaponRepository>,
@repository.getter(SkillRepository)
protected skillRepositoryGetter: Getter<SkillRepository>,
) {
super(Character, dataSource);
this.armor = this.createHasOneRepositoryFactoryFor('armor', armorRepositoryGetter);
this.weapon = this.createHasOneRepositoryFactoryFor('weapon', weaponRepositoryGetter);
this.skill = this.createHasOneRepositoryFactoryFor('skill', skillRepositoryGetter);
}
This can help you to assign weapon
, armor
, and skill
to character.
On the other hand, what we need to do for the weapon.repository.ts
is kind of the same. Instead of HasOneRepositoryFactory
, we add BelongsToAccessor
before constructor.
public readonly character: BelongsToAccessor<
Character,
typeof Weapon.prototype.id
>;
And change the constructor to this:
constructor(
@inject('datasources.mongo') dataSource: MongoDataSource,
@repository.getter('CharacterRepository')
protected characterRepositoryGetter: Getter<CharacterRepository>,
) {
super(Weapon, dataSource);
this.character = this.createBelongsToAccessorFor('character',characterRepositoryGetter);
}
Don't forget to add imports at the head of weapon.repository.ts
.
import {BelongsToAccessor, juggler, repository} from '@loopback/repository';
import {Character} from '../models';
import {inject} from '@loopback/core';
import {CharacterRepository} from './character.repository';
Then do the same thing for armor.repository.ts
and skill.repository.ts
. And our repositories are all set.
You can check my code for all repositories at here.
In this episode, we used a third-party library to generate UUID. You can easily use any external library in you LoopBack 4 project.
We also built relations between character
, weapon
, aromr
, and skill
. In a real world application, most of entities have relationships between each other. You can use LoopBack 4 to easily manage that in your project.
In our next episode, we will do a lot of coding in controller
to create weapon
, armor
, skill
and equip a character with them. Controller is where you achieve most of your project functions and business logic. I am sure we will have a lot of fun in next episode.
In the meantime, you can learn more about LoopBack in past blogs.
]]>April was a very productive month for the LoopBack team! We focused on the following areas:
Besides the items above, we landed several additional improvements. Keep reading to learn more details.
Relations like HasMany, HasOne and BelongsTo depend on the underlying database to enforce integrity and referential constraints. At the moment, LoopBack assumes those constraints are either already defined (when working with existing database schema) or created manually by the user (when creating schema from LoopBack models). This manual step is very cumbersome. Secondly, the database migration provided by LoopBack does not provide any information about such steps and thus it's easy for inexperienced users to unknowingly create database schema allowing race conditions and creating room for inconsistency in the stored data.
Ideally, we want developers to include any additional constraints in the definition of models and properties, and have our connectors translate these constraints to database-specific setup instruction as part of schema migration.
Historically, LoopBack did provide some level of support for indexes and foreign keys. Unfortunately, this was implemented at connector level and each connector provided a slightly different flavor.
To improve this situation, we started with some research on the current status of support in different connectors and proposed a new definition syntax that will become a new standard for LoopBack models.
For example, a HasOne relation needs to define a UNIQUE index and a FOREIGN KEY constraint for the property storing relational link. The proposed syntax makes this very easy to express directly in your TypeScript source code:
@model()
export class TodoListImage extends Entity {
@belongsTo(
() => TodoList,
{},
{
unique: true,
references: {
model: () => TodoList,
property: 'id',
onUpdate: 'RESTRICT',
onDelete: 'CASCADE',
},
},
)
todoListId?: number;
// other properties, etc.
}
Please refer to PR#2712 to learn more about the proposed syntax and for a list of follow-up stories created to implement the proposed functionality.
These relations work best with relational databases that support foreign key and unique constraints and attempting to use them with NoSQL databases will lead to unexpected behaviour. Our model relation documentation has been updated to reflect these limitations. See HasMany Relation, BelongsTo Relation, and HasOne Relation.
Models can now be discovered from a supported datasource by running the lb4 discover
command. See Discovering models from relational databases. This new feature was contributed by the community.
It's often desirable for various types of artifacts to participate in certain life cycles events and perform some related processing. Basic life cycle support introduces these capabilities:
lb4 observer
command to generate life cycle scripts.See Life cycle events and observers.
PR#2657 improves @inject.setter
to allow binding creation policy. It also adds @inject.binding
to resolve/configure a binding via dependency injection.
See @inject.setter and @inject.binding.
Extension Point/extension is a very powerful design pattern that promotes loose coupling and offers great extensibility. There are many use cases in LoopBack 4 that fit into design pattern. For example:
@loopback/boot
uses BootStrapper
that delegates to Booters
to handle different types of artifacts.@loopback/rest
uses RequestBodyParser
that finds the corresponding BodyParsers
to parse request body encoded in different media types.@loopback/core
uses LifeCycleObserver
to observe start
and stop
events of the application life cycles.To add a feature to the framework and allow it to be extended, we divide the responsibility into two roles:
Extension point: it represents a common functionality that the framework depends on and interacts with. Examples include booting the application, parsing http request bodies, and handling life cycle events. Meanwhile, the extension point also defines contracts for its extensions to follow so that it can discover corresponding extensions and delegate control to them without having to hard code such dependencies.
Extensions: they are implementations of specific logic for an extension point, such as, a booter for controllers, a body parser for xml, and a life cycle observer to load some data when the application is started. Extensions must conform to the contracts defined by the extension point.
See Extension point and extensions.
To support multiple authentication strategies in the revised authentication system in @loopback/authentication
that we started last month, we introduced:
@authenticate('strategy_name')
decorator to define an endpoint the requires authentication.With Node.js 12.0.0 officially released (see Introducing Node.js 12), we are investigating effort required to support this new Node.js version.
LoopBack 4 has been already updated for Node.js 12.0.0. We had to tweak few places in loopback-datasource-juggler
to make our test suite pass on the new runtime version, which you can learn more about in PR#1728.
LoopBack 3 core packages loopback
and strong-remoting
work on Node.js 12.0.0 out of the box, loopback-datasource-juggler
version 3 was fixed PR#1729.
In the next weeks and months, we are going to check our connectors and other LoopBack 3 packages like loopback-cli
. If all goes well, then all LoopBack components and connectors will support Node.js 12 by the time it enters LTS mode in October this year.
We added a new section called Working with Express middleware. This describes the differences between LoopBack REST layer and Express middleware and explains how to map different kinds of Express middleware to LoopBack concepts and APIs.
After we implemented mountExpressRouter
, we continued on the Migration epic and implemented a booter component for booting LoopBack 3 applications on a LoopBack 4 project. Lb3AppBooterComponent
can be used to boot the LoopBack 3 application, convert its Swagger spec into OpenAPI version 3, and mount the LoopBack 3 application on the LoopBack 4 project.
The component offers two modes to mount your LoopBack 3 application: the full application or only the REST router. By default, it will mount the full application, but you have the option to modify it to only the REST routes along with the base path the routes are mounted on top of.
To see how to use the component, see Boot and Mount a LoopBack 3 application.
noImplicitThis
, alwaysStrict
and strictFunctionTypes
, see PR#2704. This exercise discovered few problems in our current codebase, the non-trivial ones were fixed by standalone pull requests PR#2733, PR#2711 and PR#2728.Please note that projects scaffolded by our lb4
tool are using @loopback/build
and our shared tsconfig.json
by default. As a result, these projects may start failing to compile if they are violating any of the newly enabled checks.
We have relaxed the relation constraint checks to allow requests that include the constrained property as long as they provide the same property value as the one imposed by the constraint. See PR#2754.
In the spike of the inclusion filter, we realized the importance of supporting cyclic references.
For example:
Model CategoryWithRelations
has a property products
containing an array of model ProductWithRelations
. ProductWithRelations
has a property category
containing CategoryWithRelations
.
In April we fixed generating the JSON schema for models with circular dependencies as the pre-work of the inclusion filter. Given the following model definitions:
@model()
class Category {
@property.array(() => Product)
products?: Product[];
}
@model()
class Product {
@property(() => Category)
category?: Category;
}
Now you can call getJsonSchema(Category)
and getJsonSchema(Product)
to get the JSON schemas without running into an infinite reference error.
OpenAPI code generation for naming and typing was improved. See PR#2722.
Allow '-' to be used in in path template variable names. See PR#2724.
When using protocol mongodb+srv, the port must not be set in the connection url. See PR#497 provided by the community.
We now support the fields
filter in the loopback-connector-couchdb2
, you can retrieve the data in the Couchdb 2.0 database with specified fields. Check out the syntax of the filter in our documentation.
Add possibility to configure the foreign key constraint with 2 optional triggers for mysql : onUpdate and onDelete. See PR#370 provided by the community.
We fixed our CI failing tests in Cloudant and MongoDB connectors to have green builds on master. The Cloudant fixes required changes in Juggler and Cloudant for Cloudant/CouchDB connectors. See PR#505 for the MongoDB fix. We aim to remove unneccessary MongoDB downstream builds in Issue#509.
We've made some fixes in both Juggler and MongoDB connector to address coercion of deeply nested primitive datatypes (Number, Date etc.) as well as Decimal128
MongoDB type, These changes address coercion of the nested properties on Create and Update operations. See PR#501 and PR#1702 for the details. Unfortunately, a regression was introduced with Juggler PR#1702, and PR#1726 aims to fix it.
After almost 5 years since the initial release, LoopBack version 2 has reached end of life and will not receive any new bug fixes. Specifically, no security vulnerabilities will be fixed in this version going forward. If you haven't done so yet, then you should migrate all projects running on LoopBack 2 to a newer framework version as soon as possible. See LoopBack 3 Receives Extended Long Term Support.
LoopBack's future success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Please join us and help the project by:
"Ready to build amazing things?" asks the LoopBack 4 homepage before encouraging you to try the open source framework.
"Try LoopBack 4 now."
In this series, I'm going to do exactly that! Join me as I create an API web game using LoopBack 4.
The main purpose of this series is to help you learn LoopBack 4 and how to use it to easily build your own API and web project. We'll do so by creating a new project I'm working on: an online web text-based adventure game. In this game, you can create your own account to build characters, fight monsters and find treasures. You will be able to control your character to take a variety of actions: attacking enemies, casting spells, and getting loot. This game should also allow multiple players to log in and play with their friends.
Some brief background on myself, first. I graduated from college last year. I don't have any background on web or game development. I am sure most of you have better understanding than me on those fields. If I can do this, you can do it too - perhaps even better!
LoopBack 4 is an open source framework that can help you build REST API. You can use LB4 to automatically generate simple APIs in couple of minutes without any coding. You can even easily connect your project to many popular databases. For this project, I will use MongoDB as my database. I don't even need to know how to use MongoDB as LB4 will handle everyting for me. Isn't this like magic?
Another great advantage of LB4 is that it is extensible. LoopBack artifacts can be managed by types. New artifact types can be introduced. Instances for a given type can be added, removed, or replaced. Organizing artifacts in a hierarchy of extension points/extensions decouples providers and consumers. You can write your own extensions to augment the framework.
In this series, my goals are to build the following functionality into the game:
This is a diagram for the relationships between character
, weapon
, armor
, and skill
.
In this first part of the series, we will cover character
model.
You can check my code here for this episode.
To begin things, I will start with the easiest task: auto-generate APIs for users to create their character and connect to MongoDB.
There are some prerequisites you may want to catch up on before we start.
Basic concepts of TypeScript, Javascript and Node.js.
I also highly recommend you to check these two examples:
This episode is based on those examples. You don't have to understand how they work, just keep in mind what function we can achieve. We will dig deep into that later.
LoopBack 4 provides a CLI (command line interface) to help create your project.
Simply run lb4 app
in a folder you want to use the CLI. Disable "Docker" when it asks you to "Select features to enable in the project"
wenbo:firstgameDemo wenbo$ lb4 app
? Project name: firstgame
? Project description: firstgameDemo
? Project root directory: firstgame
? Application class name: FirstgameApplication
? Select features to enable in the project Enable tslint, Enable prettier,
Enable mocha, Enable loopbackBuild, Enable vscode, Enable repositories, Ena
ble services
There are four important components in a LB4 project: Model, Datasource, Repository, and Controller. Let's create them one by one.
A model is like the class in Java or a table in relational database. It is an entity with one or more properties. A model may also have relationships with other models. For example, a student
model could have properties like studentID
, name
, and GPA
. It may also have one or more entity of course
model and belong to a school
model.
We will delve more deeply into the model relationship in next blog. In this episode let's simply create a character
model first. The character
model has following properties:
Run lb4 model
in the project folder we just created by using CLI (run cd firstgame
first).
? Model class name: character
? Please select the model base class Entity (A persisted model with an ID)
? Allow additional (free-form) properties? No
Let's add a property to Character
Enter an empty property name when done
? Enter the property name: id
? Property type: number
? Is id the ID property? Yes
? Is it required?: No
? Default value [leave blank for none]:
Let's add another property to Character
Enter an empty property name when done
? Enter the property name: name
? Property type: string
? Is it required?: Yes
? Default value [leave blank for none]:
Let's add another property to Character
Enter an empty property name when done
? Enter the property name: level
? Property type: number
? Is it required?: No
? Default value [leave blank for none]: 1
...
The first property is id
. It's like the primary key in relational database. We don't need to specify id
as we will auto generate id
.
The second property is name
. That is the only thing we need to specify.
All of other properties like level
, attack
and defence
are default. We will not need to provide specifics.
If you go to /src/models
, you will see character.model.ts
. We don't need to do anything about it at this point. We will come back in following episode.
We connect to the database in LB4 using datasource. LB4 supports almost all of the popular databases. In this project and series I will use MongoDB. If you don't know how to use MongoDB, don't worry! LB4 will take care everything for you. You only need to install mongoDB first.
After installation, run lb4 datasource
in your project root.
wenbo:firstgame wenbo$ lb4 datasource
? Datasource name: mongo
? Select the connector for mongo: MongoDB (supported by StrongLoop)
? Connection String url to override other settings (eg: mongodb://username:password@hostna
me:port/database):
? host: localhost
? port: 27017
? user:
? password: [hidden]
? database: mongo
Fill host
with localhost
and port
with 27017
.
This will build a connection between your project and MongoDB.
The repository is like a connecter between the datasource and models. One of its jobs is to act like database injecter and extracter: when you want to create or fetch an entity of a model, repository will help you inject data into database or extract data from database.
Run lb4 repository
in your project root.
wenbo:firstgame wenbo$ lb4 repository
? Please select the datasource MongoDatasource
? Select the model(s) you want to generate a repository Character
? Please select the repository base class DefaultCrudRepository (Legacy juggler bridge)
create src/repositories/character.repository.ts
update src/repositories/index.ts
You will find character.repository.ts
in src/repositories
. It's all we need at this point.
Controller is the most important component. It contains the code for all of your project functions and handles all business logic. In this series We will spend the majority of our time on controller.
Run lb4 controller
in your project root to create default controller.
wenbo:firstgame wenbo$ lb4 controller
? Controller class name: character
? What kind of controller would you like to generate? REST Controller with CRUD functions
? What is the name of the model to use with this CRUD repository? Character
? What is the name of your CRUD repository? CharacterRepository
? What is the type of your ID? number
? What is the base HTTP path name of the CRUD operations? /characters
create src/controllers/character.controller.ts
update src/controllers/index.ts
This will generate all basic APIs for character
, including post
, get
, patch
, put
, and delete
.
If you have tried the Todo tutorial, you probably already noticed the auto increment id feature. When you call the post
API multiple times (leave id
blank), the id
increased by 1 every time. This feature is supported by the in-memory database. But we are using MongoDB in this project. If we want to have that feature, we need to do that programmatically.
Go to src/controllers
and open character.controller.ts
with your favourite editor.
@post('/characters', {
responses: {
'200': {
description: 'Character model instance',
content: {'application/json': {schema: {'x-ts-type': Character}}},
},
},
})
async create(@requestBody() character: Character): Promise<Character> {
//add following lines
let characterId = 1;
while(await this.characterRepository.exists(characterId)){
characterId ++;
}
character.id = characterId;
//add above lines
return await this.characterRepository.create(character);
}
Add those lines into the post /character
. That will traverse your database to find a unique character id. Since this is a very bad programing practice, we will try to improve it in next episode.
LoopBack 4 has a build-in API explorer for you to play and test your API.
To start your project, run npm start
in the project root.
wenbo:firstgame wenbo$ npm start
> firstgame@1.0.0 prestart /Users/xiaocase/Documents/learnlb/MyAPI/firstgameDemo/firstgame
> npm run build
> firstgame@1.0.0 build /Users/xiaocase/Documents/learnlb/MyAPI/firstgameDemo/firstgame
> lb-tsc es2017 --outDir dist
> firstgame@1.0.0 start /Users/xiaocase/Documents/learnlb/MyAPI/firstgameDemo/firstgame
> node .
Server is running at http://[::1]:3000
Try http://[::1]:3000/ping
Go to http://[::1]:3000 and open explorer. You will see this:
This shows the basic APIs we just created.
Now let's try to create a character. Open post /character
and click "try it out". You only need to input a name for character, so you can leave the others blank.
Then we can try to get information for the character. Open get /character/{id}
and click "try it out". Input "1" as character Id.
In this episode, we covered the how to create simple APIs. You can do the same to create a start point for your own project, for example, a student registration system which has a student
model with properties like studentId
, name
, major
, and course
.
On the other hand, you have the freedom to choose any database you want. LB4 supports most databases very well. Here is an example that uses SOAP webservices as datasource.
In next episode, we will add weapon
, armor
, skill
model and handle the relationship between models.
In the meantime, you can learn more about LoopBack in past blogs.
]]>When the LoopBack team released LoopBack 4 GA version last October, they didn't stop to rest on their laurels. The team has been busy enhancing the framework, closing feature parity gaps, and helping community onboard with LoopBack 4.
Since GA, we have been focusing on implementing/enhancing the following major areas:
Many thanks to your support and contributions! We are seeing more than double in our monthly download numbers in npmjs.com since GA. We are also seeing an increase in activities from the community, in terms of answering others' questions and submitting pull requests. For the last 3 months, more than 15% of the merged pull requests are coming from the community. Want to help but new to open source contribution? Don't worry, this step by step guide will guide you through the contribution process.
Let's take a closer look at each epic.
As part of the scenario-driven approach, we have added a reference implementation using JWT authentication in the shopping example. We have also worked on a more detailed design and started the implementation to enable extension points for plugging in different authentication strategies.
Besides hasMany
and belongsTo
, we have added one more relation type:
We received lots of interests around other relation types, such as hasManyThrough
. Some of you are implementing the command line interface for creating model relations (lb4 relation
). It's on the way!
Inclusion of related model when querying data is a popular feature that is not available in LoopBack 4 yet. The initial research was concluded with a need to better investigate how to represent navigational properties used to include data of related models. We have explored multiple alternatives and came up with a solution that not only nicely addresses both TypeScript types and OpenAPI schema, but is also flexible enough to support other use cases like partial updates and exclusion of auto-generated properties when creating new model instances. You can learn more in our March milestone blog post.
Earlier this year, we have made the announcement to extend the long term support for LoopBack 3. If you're using an older LoopBack version, don't miss the announcement blog post.
Meanwhile, we did a lot of investigation to improve the developer experience for migrating LoopBack 3 applications to LoopBack 4. One of the goals is to allow you to migrate your application incrementally. We have investigated on how to mount Express router with OpenAPI spec and completed a spike for mounting an LoopBack 3 app and include its REST API in OpenAPI spec.
In the coming weeks and months, we are going to implement stories identified by the spike and grouped in the epic Mount LB3 app in LB4. Stay tuned!
One of our goals for LoopBack 4 is to build a minimal core while enabling everything else to be implemented via extensions. We continued to enhance the extensibility of the framework. We have introduced context.view
and related events and added a greeter-extension
example. Lifecycle support and binding configuration/injection are coming soon.
As LoopBack is a Node.js framework, we've looked at the common use cases for Node.js developers to think of ways to make your adoption to LoopBack easier. Receiving feedback from various sources, we have:
Investigated the possibility to use JavaScript when developing LoopBack 4 applications. Check out our recent blog post for details.
Added an example on how to mount Express middleware to a LoopBack application.
On top of the features mentioned in the above stories, we are always looking to use and/or integrate with popular technologies. Here is a list of experimental features we're working on:
While we want to make sure high quality of code goes into our codebase which may take multiple iterations, we're looking for ways to release experimental features so that our users can take a peek earlier and give early feedback. We are looking for your feedback on how we can roll out the experimental features in GitHub issue #2676.
Going forward, we will continue to improve the migration experience, close feature parity gaps and bring compelling features to you. Please see the Q2 roadmap for our plan.
If you have been following our blog, you probably notice that we have monthly milestone blogs to update our users on the progress that we're making. Here are the previous monthly milestone blogs if you want more details:
LoopBack's future success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Please join us and help the project by:
We landed an outstanding number of code contributions in March, making for a very productive month! We merged 63 PRs in total, and 10 out of them are from the community. Cheers!
The team was able to make good progress of the epics we are focusing on, like LB3 to LB4 migration, adding @loopback/context
features, JavaScript experience, the authentication system, and describing model properties to be more flexible. Read more to see the details of our achievements in March.
We started to incrementally work on the migration stories created from the PoC PR. This month we implemented the Express router to allow LoopBack 4 app developers to add arbitrary set of Express routes and provide OpenAPI specifications. You can call mountExpressRouter
on either the app level or the rest server level to mount external routes. For details please check the router's documentation.
As a framework built on top of the IoC (Inversion of Control) and DI (Dependency Injection), the extension point is commonly used in LoopBack 4 to declare contracts for extension contributors to plug-in components. The existing usages of extension point include the request body parser and the boot strapper. It is also needed for supporting multiple authentication strategies. Check out the greeter extension point to learn the best practice of registering an extension point along with its extensions.
The discussion and review of a series of context enhancement PRs keeps moving. This month we landed the PR that implemented the context view feature. A context view is created to track artifacts and is able to watch the come and go bindings. More details can be found in the Context document.
We have also enforced the dependency injection for bindings with the SINGLETON
scope to make sure their dependencies can only be resolved from the owner context and its ancestors, but NOT from any of the child contexts. This is required as the value for a singleton binding is shared in the subtree rooted at the context where the binding is contained. Dependencies for a singleton cannot be resolved from a child context which is not visible and it may be recycled. See the Dependency Injection documentations for more details.
Now users can specify the scope in the @bind
decorator when annotating an artifact class with @bind
. The application level bindings are improved by honoring more configurations in the @bind
decorator. Now users could specify the binding scope and the namespace of tags as the inputs of @bind
. Details can be found in the binding document.
We solved the self relation issue and created corresponding test cases as the reference usage. You can check the documentation for handling recursive relations to learn how to create a hasMany
and belongsTo
relation to the same entity.
Before writing the extension point for plugging in different authentication strategies, we decided to do some investigation of the popular authentication mechanisms and adopted the user scenario driven development. This is to make sure the abstractions for services are common enough. The design documents for our authentication system can be found here. The document begins with illustrating a LoopBack 4 application that supports multiple authentication approaches and finally divides the responsibilities among different artifacts. The abstractions we created in March are two interfaces for the user service and the token service in @loopback/authentication
.
The initial inclusion spike left us a question: how to distinguish the navigational property from the normal model properties? This month we had a PoC to demonstrate describing the navigational model properties with a new interface along with how to generate the corresponding OpenAPI schema.
The proposed solution has two major parts:
At TypeScript level, we will introduce a new interface to describe navigational properties and a new type to describe data object holding both own properties and navigational properties. For example, when a Category
model has many Product
instances:
// Navigation properties of the Category model.
interface CategoryRelations {
products?: ProductWithRelations[];
}
// Category's own properties and navigation properties.
export type CategoryWithRelations = Category & CategoryRelations;
When decorating controller methods with OpenAPI metadata, we need to include navigational properties in the schema generated from the model definition. This will be achieved by replacing x-ts-type
extension with a call of a new helper function getModelSchemaRef
with a new flag includeRelations
:
schema: {
type: 'array',
- items: {'x-ts-type': Category},
+ items: getModelSchemaRef(Category, {includeRelations: true})
},
}
Under the hood, getModelSchemaRef
will create a new OpenAPI Schema describing both own and navigational properties of the given model and give the schema a unique title so that we can reference it from multiple places.
Please check PR 2592 for more details and the discussions we had. And the follow-up stories are created as our next target.
While researching options for describing navigational model properties, Miroslav realized that the proposed solution is easy to extend to support other kinds of schema generated from model.
To describe request body of a PATCH
request, we can introduce a new getModelSchemaRef
flag called partial
:
schema: getModelSchemaRef(Category, {partial: true}),
At TypeScript level, such data object can be described using TypeScript's Partial
type:
obj: Partial<Category>
To exclude certain properties from POST
request (e.g. id
that will be generated by the database), we can introduce another new flag called exclude
:
schema: getModelSchemaRef(TodoList, {exclude: ['id']}),
At TypeScript level, such data object can be described using Pick
and Exclude
types:
obj: Pick<TodoList, Exclude<keyof Category, 'id'>>
Once these two new flags are implemented, we will be able to fix validation of request bodies to correctly enforce required properties for operations like POST
& PUT
, and treat all properties as optional for PATCH
operations.
You can find more details in PR 2646, and the follow-up stories are outlined here.
After a thorough exploration and discussion of writing the LoopBack 4 application in Javascript this month, we summarized our findings and achievements in our "Experimenting with Plain JavaScript Programming in LoopBack 4" blog post. It talks about the LoopBack 4 artifacts that we are able to create in JavaScript and also the limitations. A plan of subsequent stories is included in the blog.
LoopBack 3 improvement: Now we allow people to define a model property called type
instead of having type
as a preserved word. Link
We added the steps to call SOAP services by running lb4 service
, see the document for calling other APIs.
There have been several conference and meet-up events happened over the last year, so we added a new section "Presentations" in the resources page to display the videos.
Add operationId based on the controller and method names. Link
Make sure the basePath
is included in the url of RestServer
. Link
Here is a summary of the contributions from our community in March. We appreciate all your attention and help!
Added the PATCH
and DELETE
method for the HasOne
relation. Link
Support specifying the type of nested properties as a model. Link
Allow the model's id property to be a number for supporting the composed key. Link
Update the mocha configuration in @loopback/build
. Link
LoopBack's future success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Please join us and help the project by:
LoopBack is a popular open source Node.js framework. Its latest version (4) is written in TypeScript, while the older version were written in JavaScript. We chose to write LoopBack 4 to make it more extensible, scalable, and sustainable. TypeScript features made it easy for us to build dependency injection in the framework and leverage it for controllers, models, and other constructs using TypeScript decorators.
We believe that TypeScript is the right move and it will help you and us in the long run. However, some developers are constrained to use plain JavaScript at the moment for various reasons. We didn't want to leave our JavaScript users behind and decided to explore the possibilities of creating a JavaScript interface to LoopBack 4. This blog post is about what we did in that regard and what we will be doing next.
We worked on a spike to enable LoopBack 4 development using JavaScript. You can take a look at a proof of concept LoopBack 4 app which uses the JavaScript API we have experimented with.
A typical LoopBack 4 app is composed of several elements:
The application also supports the ability to create custom routes to define non-REST enpoints.
The proof of concept listed above demonstrates how writing LoopBack 4 applications in JavaScript would enable all the above features.
What was Achieved
Ability to create application class in JavaScript.
Ability to create route in JavaScript.
Ability to create custom sequence in JavaScript.
Ability to create CRUD controller in JavaScript.
Ability to create custom controller in JavaScript.
Ability to create datasource in JavaScript.
Ability to create model in JavaScript.
Ability to create repository in JavaScript.
Although it allows the creation of some simple apps, it is pretty limited and inflexible in what it can do.
Limitations
The class factory pattern is not idiomatic ES6. Ideally, we should be able to use classes like in TypeScript. Making this possible is not easy, since we have to come up with a good interface and developer experience for dependency injection. This will be possible when decorators for ES6 classes becomes a JavaScript feature.
Routes are limited to simple responses and has no access to the LoopBack request object, which contains a lot of additional information.
Controller methods are hard-coded in the helper library and leave no room for customization.
Models are defined using a JSON files, instead of JavaScript classes.
The TypeScript LoopBack 4 API has a very neat and intuitve developer experience when it comes to customization and extension because of the in-built dependecny injection capability. Replicating this capability in JavaScript is the biggest hurdle in creating the JavaScript API for LoopBack 4.
We realized that creating a parallel LoopBack 4 JavaScript framework would be lot of work in terms of development and maintenace. A better approach to providing a JavaScript API for LoopBack 4 would be to identify most practical use cases and then try to come up with appropriate solutions.
To help decide our approach to creating the usecase-specific JavaScript LoopBack 4 API, instead of porting the whole framework to JavaScript, we identified several personas and went through each of them and considered their use cases.
The personas we identified are:
Large scale application developer. Since large scale application are best developed with TypeScript, we concluded that this persona should be using the TypeScript version of LoopBack 4.
Extension developer. This persona wants to extend the capabilities of LoopBack 4 by writing custom extensions. Being technical enough, this persona should also be comfortable using TypeScript.
API developer. This persona is understands the importance of OpenAPI support and the ORM capabilities provided by LoopBack 4, has only a few endpoints to expose, and prefers a simpler JavaScript API to interact with, instead of having to create controllers and classes.
Plain Express developer. This persona primarily uses Express for application development, and is interested in using the features provided by LoopBack 4, using JavaScript.
We combined personas 3 and 4 to a single one - a developer who likes the simplicity of JavaScript and wants to use LoopBack 4 APIs in Express-style routes.
So instead of porting the TypeScript LoopBack 4 to JavaScript, we have decided to cater to this specific usecase. Developers will be able to setup and customize their routes using OpenAPI specifications and will have access to the LoopBack 4 request object. Apart from providing access to other metadata added by LoopBack 4, such as authentication details, this object is the link to the LoopBack 4 internals. Via this object, developers can access repositories, datasources, models, and context.
We have started work on porting the Todo example to JavaScript. This port will show how to create LoopBack 4 routes and datasources in JavaScript.
We would like to hear from you what you think of JavaScript experience in LoopBack 4. Is it required? What are your expectations?
LoopBack's future success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Please join us and help the project by:
This past October, we announced LoopBack 4 GA is ready for production use and updated the Long Term Support (LTS) schedule in our LTS page. Due to popular requests, LoopBack 3 now receives an extended long term support with updated timeline as shown in the table below.
Version | Status | Published | Active LTS Start | Maintenance LTS Start | End-of-life |
---|---|---|---|---|---|
LoopBack 4 | Current | Oct 2018 | -- | -- | Apr 2021(minimum) |
LoopBack 3 | Active LTS | Dec 2016 | Oct 2018 | Dec 2019 (revised) | Dec 2020 (revised) |
LoopBack 2 | Maintenance LTS | Jul 2014 | Dec 2016 | Oct 2018 | Apr 2019 |
The extended period gives more time for our users to move to the new version which is a different programming model and language. It also allows us to improve the migration experience and migration guide.
Below are a few questions that our users frequently asked about different versions of LoopBack.
If you're considering using LoopBack for your next project, our recommendation is to first create a proof-of-concept application using LoopBack 4 (LB4). You may want to check if there are any features that your application requires that LB4 does not provide out of the box yet. Search LB4 GitHub issues to find any existing discussions around those missing features, as there may be 3rd-party extensions or workaround available. If you run into a missing feature that's not discussed in our issue tracker yet, then please open a new issue to let us know!
Hopefully, you will find a way how to implement all major requirements in your PoC application and can thus build your project on LB4.
If some of your requirements cannot be feasibly implemented in LB4, then you have two options:
You can work with the LoopBack team and contribute the missing parts yourself. It will cost you time to implement framework features, but you will save time on migrating your project from LB3 to LB4 later on.
Alternatively, if LB3 provides all what your project needs, then you can save upfront investment and build your project on LB3 now, preparing to pay the migration costs later in the future.
If you already have LoopBack 3 applications running in production, it is a good time for you to review the difference between LoopBack 3 and LoopBack 4. Even with the extended end-of-life date for LoopBack 3, it is never too early to start migrating your LoopBack 3 application to LoopBack 4. Please also note the feature parity gap we had identified. Your feedback is always appreciated!
We're actively working on improving the migration story! Please check out GitHub issue #1849 for discussions and progress. Miroslav is currently investigating different approaches of having a compatibility layer between LoopBack 3 and LoopBack 4 artifacts. The first step is to allow developers to mount their LoopBack 3 applications in a new LoopBack 4 project. You can follow the progress of this work in GitHub issue #2479.
If you're a LoopBack 2 user, you should at least migrate your LoopBack 2 applications to LoopBack 3. The effort should be minimal and the process should be smooth. You can read more details in the 3.0 migration guide. When LB2 goes end of life (EOL), you should not be using LB2 in production because there will be no security fixes.
LoopBack's future success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Please join us and help the project by:
It feels like 2019 just started, but we are somehow already in March. February flew by, but while the month was short, the list of things the LoopBack team accomplished in the month was the opposite. In February, we tackled authentication and authorization, spikes on migration from LoopBack 3 to LoopBack 4, preparation for events, and others. You can see the February milestone and see the March milestone to see what we are working on next. Read more to see the details of our progress in February.
We refactored the JWT authentication strategy in loopback4-example-shopping
to be more modular and leveraged functions from jsonwebtoken
to perform a more robust password hashing and comparison. The token based utilities are refactored into a token service, so that it can be injected into the controller and strategy classes using Dependency Injection. The bcrypt
password hasher service is created similarly.
Our next step is to write a guide for plugging in different authentication strategies and depicting the API flow of authenticating the endpoints. In the meantime, we will be adding more abstractions to shape the authentication system as the groundwork before opening the extension points for the authentication system. You could check the subsequent stories in issue #1035 and track our progress there.
LoopBack has a guide on migrating applications from LoopBack 2 to LoopBack 3, and it's only fitting that we include a guide on migrating applications from LoopBack 3 to LoopBack 4 as they reach feature parity. However, the latter's transition is more complicated than the former's transition. We have an epic, if you would like to see more details.
This month, we did two spikes to work on this transition. We started with a proof of concept demonstrating how to take LoopBack 3 model definition files (e.g. common/models/product.json
and common/models/product.js
) and drop them without any modifications into a LoopBack 4 project. You can find the original idea in issue #2224 and the working code in pull request #2274.
Unfortunately, this approach turned out to be too expensive to implement and maintain, and we decided to abandon it.
Not all is lost, though! While discussing the proof of concept, we realized there is a simpler way how to build a bridge between LoopBack 3 and LoopBack 4: mount the entire LoopBack 3 application as a REST component of the LoopBack 4 project.
The pull request #2318 presents a proof of concept that we will use to drive the actual implementation tracked by Epic #2479.
We have also identified few new stories to bridge the gap preventing LoopBack 3 applications to be migrated to LoopBack 4, see the following GitHub comment.
We added a new feature to the CLI: the --docker
option when generating a LoopBack application. This option generates Dockerfile
, .dockerignore
, and two Docker scripts: docker:build
and docker:run
. See Application generator to see how to generate an application with --docker
.
Following this feature, we added a fix that forces the test host to be HOST
environment variable or IPv4 interface, which makes it easier to run LoopBack 4 application tests inside a Docker container.
We introduced a detailed list of steps to follow if you want to submit a pull request for LoopBack 4. This guide includes steps for beginners and for experienced users. It took a lot of discussion to finally nail a balanced read that was both concise and informative. You can now follow this handy resource if you would like to submit a PR to loopback-next
.
We added a new tutorial demonstrating how to mount LoopBack 4's REST API on an Express application. Users can now mix both the Express and LoopBack 4 frameworks in order to best match their own use cases. In this tutorial, we mounted a Note
application created by the LoopBack 4 CLI on top of a simple Express server and served a static file. You can follow the tutorial or see the completed example by using the command lb4 example express-composition
.
In a series of incremental pull requests, we reworked our project layout, moved all test files from test
to src/__tests__
directory and updated TypeScript build configuration to place files directly to dist
folder, instead of dist/src
and dist/test
. This change simplifies the build setup and unifies file references between TypeScript sources and JavaScript runtime. It allows us to further improve our project infrastructure, for example start using TypeScript Project References.
LoopBack 4 projects scaffolded with recent versions of lb4
tool will use the new layout too.
Existing projects can be updated with a bit of manual work:
test
to src/__tests__
.package.json
to use the new test location.tsconfig.json
: set rootDir
to "src"
, remove "index.ts"
and "test"
entries from the include
field.import
statements.The pull request #2316 shows how we updated our example applications; you can use it as a reference guide.
You can now disable the OpenAPI spec endpoints (e.g. /openapi.json
) which will also disable the /explorer
endpoint by setting your rest's openApiSpec.disabled
option to true. See Customize How OpenAPI Spec is Served for more rest.openApiSpec
options. PR #2470.
Another rest
option introduced is requestBodyParser
, so you can now configure the request body parser. PR #2432.
LoopBack cares a lot about your security. A security issue related to JSON.parse()
was discovered and this PR added a sanitizer for JSON. PR #2348.
Now you can override the default Express settings and also add your own. PR #2423.
You can now use a custom repository base class in your LoopBack application. PR #2235.
This month, the team went to downtown Toronto for a meetup. This included an overview of LoopBack 4, along with demonstrations of what LoopBack 4 can do. Check out the blog post about it. There was also a Quick Lab and Master Class session for LoopBack 4 in IBM's Code@Think in mid-February. And finally, Raymond presented at DeveloperWeek 2019 where he talked about Building APIs with Node.js, TypeScript, and LoopBack.
If you want to come to our future events, keep an eye out on the Strongblog for announcements.
As the number of contributions from our community rises, we are spending an increasing part of our time on reviewing these pull requests and helping our volunteers to get their changes landed. In fact, every fifth pull request opened this month was contributed by you! Check out the community-contribution label to see pull requests by the community.
We would like to take a moment to thank everyone who has submitted a pull request; the team really appreciates your contributions.
There are also other ways for getting involved beyond code contributions. Triaging issues and reviewing pull requests are examples of activities that would help us to accelerate the success of LoopBack as an open-source project. You can learn more about different contribution opportunities in Contributing to LoopBack.
LoopBack's future success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Please join us and help the project by:
The Toronto Cloud Integration Meetup hosted an event on Tuesday, February 5, 2019: "Quickly Build APIs with Existing Services and Data Using LoopBack!" Since not everyone is local, we filmed the meetup so we could share it with our community.
Our goal was to bring awareness of LoopBack to the Node.js community in Toronto. We also brought snacks and swag to share. Here's a snapshot of what was covered.
Janny Hou explained what LoopBack is, what you can do with it, and the rationale behind the rewrite of the framework. You can find the link to relevant LB4 docs here.
Biniam Admikew demonstrated how how easy it is to expose REST API from your database with just a few steps.
Jamil Spain provided an additional demo while also taking care of capturing the meetup on video.
You can view each videos on our YouTube channel playlist here.
You can view the slides from the meetup on Slideshare.
We've provided news, tutorials, and updates on LoopBack for almost as long as the StrongLoop site existed, and continued to do so after IBM acquired StrongLoop in 2015. Recently, IBM created another resource for LoopBack! In addition to accessing news of the open-source Node.js API Framework on this site, you can now also find news, code and more on the official IBM Developer LoopBack page.
The site clearly explains what LoopBack is, what you can use it for, and what it includes:
LoopBack is a highly extensible, open-source Node.js framework based on Express that enables you to quickly create dynamic end-to-end REST APIs and connect to backend systems such as databases and SOAP or REST services.
With LoopBack, you can easily create models based on a schema (if you have one) or define models if you don’t and build relationships between models.
LoopBack 4 is the latest release from the LoopBack team and includes:
- A new core to deliver great extensibility and flexibility written in TypeScript/ES2017.
- Creation experience for defining REST APIs and handling API requests and responses.
- Programming model with dependency injection and concepts like components, mixins, and repositories to make LoopBack extensible.
- GraphQL support through OASGraph.
In addition, the site provides a diagram displaying how LoopBack interacts with incoming requests and outgoing integrations as well as LoopBack's various capabilities. It also outlines the benefits of contributing to the project and how LoopBack addresses certain business problems.
Check it all out on the official IBM Developer page!
Learn about other open source projects that provide key technologies for the API economy: API Microgateway; OpenAPI Spec; and of course LoopBack.
We're one month into the new year! While the team had some time off extending into January, we still managed to work and spike on authentication, migration from LB3, user adoption, extensibility, and documentation. Read more to find out how it all unfolded.
Last month we implemented the strategy resolver, JWT strategy class and authentication action and created the diagram that depicts their dependency relations. Based on those efforts, in January we enriched the loopback4-example-shopping
repository with a working JWT authentication system, and added two endpoints for model User
to make use of it:
POST /Users/login
verify a user's credentials and return a valid JWT access tokenGET /Users/me
display the logged in user of the applicationThe following picture describes how the authentication mechanism works:
This month our discussion focused on dividing the responsibilities among controller, repository and services/utilities.
The login related logic should be extracted into a service which could be shared among different clients, like REST, gRPC and WebSocket. Those logic include taking in credentials, verifying users, generating and decoding access token. The login service receives User's repository via DI. As the first implementation, we simply keep them as utils. They will be refactored into service in story Refactor authentication util functions into a service
The controller function should extract credentials like email, username and password from the request. And calls the service to perform login. The service is injected via DI.
We also discovered there are three extension points that are needed in order to make LoopBack's authentication system more flexible. We need extension points such as:
@loopback/authentication
.PR https://github.com/strongloop/loopback-next/pull/2249 illustrates extension point/extension pattern is in progress. It provides a standard to make extension point that the 3 above stories could follow.
A more detailed tutorial will be created after finishing the story Refactor authentication util functions into a service.
In order to cater to users developing LoopBack applications with JavaScript, Yaapa conducted a spike on how to get a LoopBack 4 JavaScript application up and running in #1978.
You can checkout the hack branch of loopback4-example-javascript
and preview the progress and the possible JavaScript API for LoopBack 4. It is a working example, feel free to experiment.
Also, in PR #795, Nora improved the UX for users on loopback.io
by setting up redirects for the current LoopBack 4 website and LoopBack 3 in Active LTS, akin to how Node.js has the split for the different version downloads in their main landing page.
We continue to refine our extensibility story as we build more extensions.
BindingFilter
to match multiple bindings using a function.find
and findByTag
.filterByKey
and filterByTag
utility functions.To make it easy to implement the extension point/extension pattern, we're building up new features for @loopback/context
. There are number of PRs under review.
context
to emit bind
and unbind
eventsPR#2122 (depends on #2291)
ContextView
to dynamically resolve values of matching bindings@inject.view
to support dependency injection of multiple bound values@inject
and @inject.getter
to support multiple bound valuesContext
to configure bindings@inject.config
to simplify injection of configurationYou are welcome to join our discussions in these pull requests. Please be aware that such PRs can be changed or abandoned during the review process.
We're always striving to have better documentation for our users. In PR#2214, Nora added much needed descriptions to our relation decorators page with clear examples on how they are applied. Moreover, Dominique wrote an excellent guide on how to publish a LoopBack 4 application to Kubernetes on IBM Cloud in PR#2160. Check it out here.
In January, Miroslav implemented a proof of concept showing a compatibility layer that would allow application developers to take their model files (.json
and .js
) from an existing LB3 project and drop them mostly as-is to a LB4 application.
The idea is to simplify the migration path from LB3 to LB4 by allowing developers to update their existing applications from LB3 to LB4 runtime (and dependencies) without having to rework their source code to the new programming model yet.
With the proposed compatibility layer, LB3 models are "upgraded" to use:
loopback-datasource-juggler
version 4.x (instead of 3.x)@loopback/rest
for REST APIs (instead of strong-remoting
)@loopback/rest-explorer
for API Explorer (instead of loopback-component-explorer
)The migrated models will run fully on LB4 runtime and thus enjoy longer LTS.
If you have an LB3 application and considering upgrading to LB4, then please join the discussion in PR#2274. Your feedback is very important to us!
While waiting for more feedback from our users, Miroslav reviewed early input and started to look into ways to mount an entire LB3 application inside an LB4 project. While such solution still depends on LB3 runtime that will eventually go out of support, it will provide almost 100% backwards compatibility and require very little code changes. Let us know if this option would be useful for your project and leave a comment in [PR#2318]strongloop/loopback-next#2318).
The LoopBack team hosted a meetup in downtown Toronto on February 5th, 2019. We taught users all about LoopBack 4 and demonstrated the capabilities and integrations of the framework. We worked hard to prepare presentations and demos for the meetup during this month. If you are in the Toronto area and are interested in future meetups, check out the Toronto Cloud Integration Meetup Group and make sure to sign up!
February is an event-filled month. Besides the meetup in Toronto, there will be LoopBack coverage at Code @ Think in Node.js Master Class and as one of the Quick Labs. Raymond will be presenting at DeveloperWeek on Feb 21 on the topic -- Speed and Scale: Building APIs with Node.js, TypeScript and LoopBack.
Twitter is a great way to stay in the loop with StrongLoop and LoopBack news. The best way to learn about events we are part of is generally https://strongloop.com/events/.
We'd like to introduce our new LoopBack development team member Dominique at our Markham lab. Dominique brings lots of experience and knowledge from working in Message Broker and App Connect development team in the past. He has already given us a step-by-step tutorial on how to deploy a LoopBack 4 application to Kubernetes in IBM Cloud here. Welcome, Dominique!
LoopBack's future success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Please join us and help the project by:
The Toronto Cloud Integration Meetup is hosting an event on Tuesday, February 5, 2019. The subject is near and dear to our hearts: "Quickly Build APIs with Existing Services and Data Using LoopBack!" If you are in town, we'd love to meet you as we cover this topic.
We interact with APIs regularly through our mobile and desktop devices! Modern apps and websites integrate them, and we want to help you build APIs off of existing services and data quickly and easily. To do so, some of our LoopBack 4 developers will demonstrate creating an API on top of databases and services in just a few steps, as well as show you how to test it. We will also have developers discuss integrating LoopBack with the latest technologies - Blockchain, Kafka, and more!
One of the purposes of this meetup is to bring awareness of this awesome Node.js framework to the Node.js community in Toronto. Of course, if you’re not using Node.js at this moment, feel free to join us and learn more!
Speakers from the LoopBack and IBM team include:
Janny Hou - Since it’s our first LB4 Meetup, we’ll keep things light and interactive in this main presentation. We will share what LoopBack is and what you can do with it. We will also tell some of the story about the rationale behind the rewrite of the framework.
Biniam Admikew - This demonstration will show how easy it is to expose REST API from your database with just a few steps. We will go a bit deeper on the concepts and what’s happening in each steps.
Jamil Spain will provide an additional demo.
The meetup will be held at 120 Bloor Street East in Toronto, Ontario. There will also be some snacks and swag on hand, as well as a great chance to network with like-minded folks.
You can register for the event here!
Visit the LoopBack 4 homepage to learn more.
Do a deeper dive to learn how to simplify your database migrations using LoopBack 4.
See how you can deploy LoopBack 4 applications to IBM Cloud.
Happy New Year! We hope everyone had a wonderful holiday and warmly welcomed 2019. This past December was a short month due to the vacation days, but we were still able to complete several fixes and take off in our next focused epics like authentication and building a JavaScript LoopBack 4 application. You can read more about our progress below.
In December we received several document updates and code refactor PRs to improve our code base and user experience. Our LoopBack team appreciates all the contributions that community members have made through the past year. We look forward to continually improving LoopBack 4 with you in the new year! Thank you for participating in the new era of LoopBack.
For the first refactoring task of the authentication component, we started by implementing a JWT strategy in the shopping example. We did this to discover the common abstractions among different authentication strategies, and to explore ways of defining the default LoopBack 4 User model integrated with authorization methods like login and logout.
During the implementation, we found some twisted concepts that could confuse people in terms of how each layer's metadata are injected and the mechanism of how the authentication component works. We created a diagram depicting the dependency relations and functionality of each concept to guide community contributors. You can find it in this comment.
We were able to write a PoC PR with the following elements:
The PR is still in progress. We will summarize the required elements and the steps to create them in the document when it's done. Our next focus would be extracting the pieces in the PoC PR into proper modules, including our existing @loopback/authentication
package or a new created extension package. You can check story #1997 to track the discussions.
With the hasMany
relation feature in place and with a lot of request from our community, it made logical sense to implement hasOne
relation next. The first iteration 1879 involved an acceptance test to drive the feature with tests for create
and get
or find
methods. However, it was using a race-condition prone approach in order to guarantee that a single related model instance was created for a source instance. The code called the find()
and create()
methods on the target repository which can lead to multiple target instances based on the asynchronous nature of those methods (see this comment for a clear example).
In order to address this issue, Miroslav proposed using the target model's Primary Key as the Foreign Key from the source model and let the underlying database enforce uniqueness. With it came belongsToUniquely()
decorator which is used as part of the target model definition to mark a property as a non-generated id
field for use with hasOne
relation. After making sure it works with in-memory and MySQL databases, we concluded that it was universal enough and made sense to delegate uniqueness enforcement at the database level and landed the PR.
However, Raymond pointed out that this approach does not indeed enforce uniqueness across all supported connectors. Instead, he proposed to mark the foreign key as a unique index. While Biniam started the implementation in 2126, he discovered that some connectors like in-memory
and cloudant
do not have support for unique indexes
. Raymond, Miroslav, and Biniam discussed HasOne's unique constraint for the foreign key and discovered a new way of looking at relations and constraints:
Given this and the fact that we don't have a concrete way to enforce referential integrity in LoopBack from the approaches taken thus far, #2126 was put on hold and 2147 aimed to remove the implementation which used target model's PK as the FK for a hasOne
relation. The onus is now on users to make sure that referential integrity is enforced by the database they're using by defining a unique index as shown in our docs. For more discussion, see 2127 and 1718.
To explore a good approach for doing the inclusion traverse, we did a spike that implemented an inclusion handler as a function to fetch the included items. When a one to many relation is established, the repository of the source model registers the inclusion handler, which takes in the target model's repository getter, applies constraints and invoke the target repository's find
method with the inclusion filter.
This works well for hasMany
relation but exposes more high level design problems when implementing the belongsTo
inclusion handler. The circular dependency restriction results in two models could not define each other as a property in the model class. Therefore the belongsTo
relation only has a foreignKey id property but not a relation property in the source model, and as a consequence, the source model couldn't really describe the data included from its parent.
Here is a summary of the things we could re-design and improve:
The discussion is tracked in story 2152.
To make users more easier to contribute the code, we cleaned up the Reporting issues page with a more detailed guide of reporting in appropriate channels, and updated the instructions of filing Loopback 2/3 and Loopback 4 bugs separately.
We fixed the AuthenticationBindings
and StrategyAdapter
's API document in the authentication module.
The API client sends an HTTP request with Content-Type
header to indicate the media type of the request body. Now we added 6 built-in body parsers for different content types and also allow users to register custom body parser as extensions by implementing the BodyParser
interface and bind it to the application.
To know more about the details, you can read the blog The journey to extensible request body parsing
We are working on a JavaScript abstraction for LoopBack 4. Using this, JavaScript developers will be able to develop LoopBack 4 applications without having to use TypeScript. You can preview an app using the progress we have made so far at https://github.com/strongloop/loopback4-example-javascript.
The next step is to further optimize the abstraction and make it as seamless as possible.
We enabled adding the base path of the REST server by calling the app method app.basePath('/abasepath')
or server method server.basePath('/abasepath')
. The base path can be provided in the configuration too:
const app = new RestApplication({
rest: {
basePath: '/api',
},
});
Previously when creating LoopBack 4 artifacts by lb4 openapi
, the controller class used inline TypeScript type literals to describe the object/array parameters for anonymous schemas. Therefore we introduced a flag called --promote-anonymous-schemas
to generate separate model classes/types for them. A good use case for turning on the flag would be generating models for the anonymous objects that describe a POST/PATCH operation's responses. For a more detailed usage of the flag and the conventions of the created LoopBack 4 model, please check the documentation of OpenAPI generator
To avoid duplicating the binding configuration every time the users are applying the same attributes such as tags and scope, we allow applying a template function to a binding in the following way:
export const serverTemplate = (binding: Binding) =>
binding.inScope(BindingScope.SINGLETON).tag('server');
const serverBinding = new Binding<RestServer>('servers.RestServer1');
serverBinding.apply(serverTemplate);
We also polished the binding related documentation and extracted them into a standalone page Binding
The built-in no-unused-variable
rule raised many issues like conflicts with other rules and it's also been deprecated. As a solution, we are replacing it with another configuration property no-unused
. This causes a possible breaking change and therefore a new standalone package @loopback/tslint-config
is created to separate the major version bump for changing the tslint configurations from @loopback/build
. You can check PR#2159 for details.
Since LB2 will reach its end of line by April 2019, we're searching through the modules in the StrongLoop organization and updating LoopBack dependencies from version to 2 to version 3. We updated loopback-workspace
from a LoopBack 2 application to LoopBack 3 application without changing any behaviours of its APIs. As part of the update, the WorkspaceEntity
and Definition
models were removed from the model configuration file, as they are not meant to be accessed directly and they are not attached to a datasource. Finally, the application has been updated to use Node 6+ and the application's dependencies have also been updated to their latest versions.
You can find out more in our LTS schedule.
LoopBack's future success depends on you. We appreciate your continuous support and engagement to make LoopBack even better and meaningful for your API creation experience. Please join us and help the project by: