Talking is cheap! Show me code!

Source address:Deno Restful API With PostgreSql & TDD

Introduction to the

Deno is Ryan Dahl’s new project. The 1.0.0 release of Deno has caused quite a buzz in the development community, and it works in much the same way as Node.

In software development, TDD development can effectively improve project quality and development efficiency in order to develop maintainable, high-quality programs.

In this post, I’ll use Deno, Typescript, and PostgreSql to develop a user management API.

Deno & oak

The following are from the official website introduction, written very easy to understand, I do not need to read.

Deno

Deno is a simple, modern, and secure JavaScript and TypeScript runtime environment based on the V8 engine and built in the Rust programming language.

  • Default security Settings. Unless explicitly enabled, there are no files, no network, and no access to the running environment.
  • Built-in support for TypeScript.
  • There is only a single executable file.
  • Built-in utilities, such as dependency checker (deno Info) and code formatting tool (deno FMT).
  • A set of audited (audited) standard modules to ensure compatibility with Deno: DENO. land/ STD.

oak

A middleware framework for Deno’s net server 🦕

Oak is a high-performance framework developed by referring to the design ideas of Node framework Koa, and its ideas of onion model middleware are also very convenient to use in development.

The target

Based on the above basic knowledge, we plan to develop a user management API platform; The backend simply provides add, delete, modify and check (CURD) operations about the user. So our main goal is to provide four interfaces to user Curds.

tool

He that would do a good job must sharpen his tools.

The development tools

VS Code, Docker

Environmental tools

Deno, Typescript, Node

Note: Node is used to debug Deno

Basic Environment Information

My environment information is as follows:

❯ node -v
v12.13.0

❯ deno --version
deno 1.2.0
v8 8.5.216
typescript 3.9.2

❯ docker --version
Docker version 19.03.8, build afacb8b

Copy the code

Other information

type version note
PostgreSql 12
PGAdmin latest

The project structure

❯ tree -l 1 Deno-restful api-with-postgresql-tdd Deno-restful api-with-postgresql-tdd ├── github // Github action ├─ .vscode // debug and vscode configuration file ├── LICENSE // Repository LICENSE ├── readme.md // Project description, including database connection, │ ├── IaaS // Infrastructure, │ ├── httpClient // ├─ migration │ ├── deps.ts // Json // Check integrity and lock files, see: https://nugine.github.io/deno-manual-cn/linking_to_external_code/integrity_checking.html ├ ─ ─ a makefile / / to simplify the development need of the command line after the directory ├── SRC // ├─ tests // Directories, 5 filesCopy the code

The implementation process

First of all, I think it is not necessary to write the whole development process in text, so I will take the initial health and addUser(POST interface) as an example, other interfaces please refer to the code implementation.

Start the infrastructure (database) and initialize the tables

Starting the database

❯ make db CD./_resources/Iaas && docker-compose up -d Starting iaas_db_1... done Starting iaas_pgadmin_1 ... doneCopy the code

The loginpgadminIn the default databasepostgresIn the newQueryPerform the following operations to initialize the database

CREATE TABLE public."user"
(
    id uuid NOT NULL,
    username character varying(50)  NOT NULL,
    registration_date timestamp without time zone,
    password character varying(20)  NOT NULL,
    deleted boolean
);
Copy the code

SRC Final directory

❯ tree -a-L 4 ├── Utils │ ├─ class.ts │ ├─ config.ts │ ├─ controllers │ ├── UserController. Ts │ ├─ health └ ─ ─ model │ └ ─ ─ IResponse. Ts ├ ─ ─ the entity │ └ ─ ─ the User. The ts ├ ─ ─ exception │ ├ ─ ─ InvalidedParamsException. Ts │ └ ─ ─ Ts │ ├── index.ts │ ├─ error. Ts │ ├─ logging. ts │ ├─ time Ts ├── Router-.ts ├─ Services ├── UserService.ts ├─ fetchResource. Ts 8 directories, 16 filesCopy the code

Before we start, let’s define some common structures and objects, such as response, exception, etc

// src/controllers/model/IResponse.ts
export default interface IResponse {
  success: boolean; // Indicates whether the request was successfulmsg? :String;     // Some log information when an error occurreddata? :any;       // The data returned to the front end when the request succeeds
}
Copy the code
// src/entity/User.ts
export default interfaceIUser { id? :string; username? :string; password? :string; registrationDate? :string; deleted? :boolean;
}
export class User implements IUser {}

Copy the code

Exceptions are used to handle error situations. When we finally return the result to the user, we can’t return the exception to the user, but in a more friendly way. See SRC/Middlewares /error.ts for details.

// src/exception/InvalidedParamsException.ts
export default class InvalidedParamsException extends Error {
  constructor(message: string) {
    super(`Invalided parameters, please check, ${message}`); }}Copy the code
// src/exception/NotFoundException.ts
export default class NotFoundException extends Error {
  constructor(message: string) {
    super(`Not found resource, ${message}`); }}Copy the code

Dependency management

Deno doesn’t have package.json like Node to manage dependencies, because Deno’s dependencies are decentralized, using remote files as libraries, much like Golang.

I store the dependencies used in the system in the root directory deps.ts, and do an integrity check and lock file at the final commit to ensure that all my dependencies are the same as those of other collaborators.

First import the test-related dependencies used. Please add any dependencies you will use in future development to this file. I’ll list the important ones.

export {
  assert,
  equal,
} from "https://deno.land/std/testing/asserts.ts";

Copy the code

test-first

Now create a new test in the tests directory named index.test.ts and write a basic test to prove that the test and program work.

import { assert, equal } from ".. /deps.ts";
const { test } = Deno;

test("should work".(a)= > {
  const universal = 42;
  equal(42, universal);
  assert(42 === universal);
});

Copy the code

Run the test for the first time

❯ make test
deno test --allow-env --allow-net -L info
Check file:///xxxx/deno-restful-api-with-postgresql-tdd/.deno.test.ts
running 1 tests
test should work ... ok (6ms)

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (6ms)
Copy the code

Setting up test firmware

The common test information used in the tests is stored in the test firmware (testFixtures), which allows reuse in the tests and simplifies the code.

// tests/testFixtures.ts
export const TEST_PORT = 9000
Copy the code

The health interface

The Health interface serves as an exit for the system health check, which is very practical in the operation and maintenance platform. For this interface, all we need to do is return a status OK. Other cases can be ignored. Then the corresponding Todo should look like this:

When the system is accessed, the state of the system, OK, should be returned.

So, the test code looks like this:

import {
  assertEquals,
  Application,
  Router,
} from ".. /.. /deps.ts";
import { getHealthInfo } from ".. /.. /src/controllers/health.ts";
import {TEST_PORT} from '.. /testFixtures.ts'

const { test } = Deno;

test("health check".async() = > {const expectResponse = {
    success: true,
    data: "Ok"};const app = new Application();
  const router = new Router();
  const abortController = new AbortController();
  const { signal } = abortController;

  router.get("/health".async ({ response }) => {
    getHealthInfo({ response });
  });

  app.use(router.routes());

  app.listen({ port: TEST_PORT, signal });

  const response = await fetch(` http://127.0.0.1:${TEST_PORT}/health`);

  assertEquals(response.ok, true);
  const responseJSON = await response.json();

  assertEquals(responseJSON, expectResponse);
  abortController.abort();
});

Copy the code

given

  • In the above code, we first declare our expected data structure, i.eexpectResponse;
  • Then you create an application and a route,
  • Create a controller that terminates the application and get the signal id from it,
  • Next, add one to the routehealthRouting and its handler;
  • Then hang the route to the application;
  • Listen for application ports and incoming application signals.

when

  • Send a GET request to the started application/health;

then

  • According to the result of the fetch to determine, see the receivedresponseIs it as expected and terminates the above application at the end.
  • At this point, if you run the test, you’re bound to get an error, and the solution is simply to implement itgetHealthInfoThe handler.

implementationgetHealthInfo handler

Create health.ts under SRC /controller and implement the desired result in the simplest possible way:

// src/controllers/health.ts
import { Response, Status } from ".. /.. /deps.ts";
import IResponse from "./model/IResponse.ts";

export const getHealthInfo = ({ response }: { response: Response }) = > {
  response.status = Status.OK;
  const res: IResponse = {
    success: true,
    data: "Ok"}; response.body = res; };Copy the code

Run the test

Run the test command and the test passes.

❯ make test
deno test --allow-env --allow-net -L info
Check file://xxx/deno-restful-api-with-postgresql-tdd/.deno.test.ts
running 2 tests
test should work ... ok (6ms)
test health check ... ok (3ms)

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (9ms)
Copy the code

So far, use TDD to complete the first simple health interface; However, the interface is not exposed, so you need to implement an application that exposes the interface in the SRC directory.

newconfig.tsTo make the application’s configuration management file
// src/config.ts
const env = Deno.env.toObject();
export const APP_HOST = env.APP_HOST || "127.0.0.1";
export const APP_PORT = parseInt(env.APP_PORT) || 8000;

export const API_VERSION = env.API_VERSION || "/api/v1";

Copy the code

The configuration file records the default host, port, and database information to start the application, and finally records the prefix of the application API.

To begin, you need to introduce the required libraries in deps.ts;

export {
  Application,
  Router,
  Response,
  Status,
  Request,
  RouteParams,
  Context,
  RouterContext,
  helpers,
  send,
} from "https://deno.land/x/oak/mod.ts";
Copy the code
New routingrouter.ts, the introduction ofHeath.tsBind routes
// src/router.ts
import { Router } from ".. /deps.ts";
import { API_VERSION } from "./config.ts";
import { getHealthInfo } from "./controllers/health.ts";

const router = new Router();

router.prefix(API_VERSION);
router
  .get("/health", getHealthInfo)
export default router;

Copy the code
newindex.tsTo build the application
// src/index.ts
import { Application, send } from ".. /deps.ts";
import { APP_HOST, APP_PORT } from "./config.ts";
import router from "./router.ts";


export const listenToServer = async (app: Application) => {
  console.info(`Application started, and listen to ${APP_HOST}:${APP_PORT}`);
  await app.listen({
    hostname: APP_HOST,
    port: APP_PORT,
    secure: false}); };export function createApplication() :Promise<Application> {
  const app = new Application();
  app.use(router.routes());
  return Promise.resolve(app);
}

if (import.meta.main) {
  const app = await createApplication();
  await listenToServer(app);
}
Copy the code
Start the application

In VSCode, you can use the F5 function key to quickly start the application, and you can start debugging in earlier versions of VSCode (below 1.47.2). You can also run the following command to start it;

❯ make dev deno run --allow-net -- allow-env. / SRC /index.ts The database link is successful! Application started, and Listen to 127.0.0.1:8000Copy the code
Call the interface test results

VS Code’s Rest Client plug-in was used here to assist in testing.

Request body
/ / _resources httpClient healthCheck. HTTP GET http://localhost:8000/api/v1/health HTTP / 1.1Copy the code
Request the results
HTTP/1.1 200 OK
content-length: 28
x-response-time: 0ms
content-type: application/json; charset=utf-8

{
  "success": true."data": "Ok"
}
Copy the code

So far, the first interface has been completed, Oak provides application services, and has been tested by Unit Test and RestClient. Complete the Todo you started.

Adding a user interface (addUser)

Adding users involves controllers, services, and Repositories, so we implement this interface in three steps.

Controller

The Controller is the control layer and provides services externally. Adding a user interface can add a user to the system, so the corresponding Todo is as follows:

  • Enter a user name and password to return user information for a specific data structure
  • Parameter must be entered, otherwise throw exception
  • If an incorrect parameter is entered, an exception is thrown

Along the way, we need to mock third party dependencies.

Import the required dependencies and create userController.test. ts. UserService is required in the Coding process, but addUser is not required. The test is as follows:

// tests/controllers/UserController.test.ts
import {
  stub,
  Stub,
  assertEquals,
  v4,
  assertThrowsAsync,
  Application,
  Router,
} from ".. /.. /deps.ts";
import UserController from ".. /.. /src/controllers/UserController.ts";
import IResponse from ".. /.. /src/controllers/model/IResponse.ts";
import UserService from ".. /.. /src/services/UserService.ts";
import IUser, { User } from ".. /.. /src/entity/User.ts";
import InvalidedParamsException from ".. /.. /src/exception/InvalidedParamsException.ts";
import {TEST_PORT} from '.. /testFixtures.ts'

const { test } = Deno;


const userId = v4.generate();
const registrationDate = (new Date()).toISOString();

const mockedUser: User = {
  id: userId,
  username: "username",
  registrationDate,
  deleted: false}; test("#addUser should return added user when add user".async() = > {const userService = new UserService();
  const queryAllStub: Stub<UserService> = stub(userService, "addUser");
  const expectResponse = {
    success: true,
    data: mockedUser,
  };
  queryAllStub.returns = [mockedUser];
  const userController = new UserController();
  userController.userService = userService;

  const app = new Application();
  const router = new Router();
  const abortController = new AbortController();
  const { signal } = abortController;

  router.post("/users".async (context) => {
    return await userController.addUser(context);
  });

  app.use(router.routes());

  app.listen({ port: TEST_PORT, signal });

  const response = await fetch(` http://127.0.0.1:${TEST_PORT}/users`, {
    method: "POST",
    body: "name=name&password=123",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",}}); assertEquals(response.ok,true);
  const responseJSON = await response.json();

  assertEquals(responseJSON, expectResponse);
  abortController.abort();

  queryAllStub.restore();
});

test("#addUser should throw exception about no params given no params when add user".async() = > {const userService = new UserService();
  const queryAllStub: Stub<UserService> = stub(userService, "addUser");
  queryAllStub.returns = [mockedUser];
  const userController = new UserController();
  userController.userService = userService;

  const app = new Application();
  const router = new Router();
  const abortController = new AbortController();
  const { signal } = abortController;

  router.post("/users".async (context) => {
    await assertThrowsAsync(
      async() = > {await userController.addUser(context);
      },
      InvalidedParamsException,
      "should given params: name ...",); abortController.abort(); queryAllStub.restore(); }); app.use(router.routes()); app.listen({ port: TEST_PORT, signal });const response = await fetch(` http://127.0.0.1:${TEST_PORT}/users`, {
    method: "POST",
    body: "",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",}});awaitresponse.body! .cancel(); }); test("#addUser should throw exception about no correct params given wrong params when add user".async() = > {const userService = new UserService();
  const queryAllStub: Stub<UserService> = stub(userService, "addUser");

  queryAllStub.returns = [mockedUser];
  const userController = new UserController();
  userController.userService = userService;

  const app = new Application();
  const router = new Router();
  const abortController = new AbortController();
  const { signal } = abortController;

  router.post("/users".async (context) => {
    await assertThrowsAsync(
      async() = > {await userController.addUser(context);
      },
      InvalidedParamsException,
      "should given param name and password",); abortController.abort(); queryAllStub.restore(); }); app.use(router.routes()); app.listen({ port: TEST_PORT, signal });const response = await fetch(` http://127.0.0.1:${TEST_PORT}/users`, {
    method: "POST",
    body: "wrong=params",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",}});awaitresponse.body! .cancel();Copy the code

The Controller layer calls the service; As a service, the controller is a third-party service, so mock the method of the service and pass it as a parameter. The following code is the application of the mock;

  const userService = new UserService();
  const queryAllStub: Stub<UserService> = stub(userService, "addUser");
  const expectResponse = {
    success: true,
    data: mockedUser,
  };
  queryAllStub.returns = [mockedUser];
  const userController = new UserController();
  userController.userService = userService;
Copy the code

#addUser should return added user when add user is added.

given
  • mock UserService, to the UserServiceaddUserMethod to pile and return a specific user structure;
  • Create a test service and setUserControllerRegister with the POST interface/users;
when
  • Pass in the correct form parameter, usingfetchrequesthttp://127.0.0.1:9000/users;
then
  • The results obtained are judged, and the test application is interrupted, and the piling method is resumed.

#addUser should throw exception about no params given no params when add user; The given and when parameters in the first test do not look up much, but the body parameter is null; The most important difference is that this time the “THEN” is inside the “when”. Since the throw exception is thrown on the handler, the “THEN” decision needs to be placed on the handler. Deno’s assertThrowsAsync is used to catch and determine exceptions.

given
  • mock UserService, to the UserServiceaddUserMethod to pile and return a specific user structure;
  • Create a test service and setUserControllerRegister with the POST interface/users;
when
  • tobodyPass an empty parameter, withfetchrequesthttp://127.0.0.1:9000/users;
then
  • thenPart in thegivenRoute processing based onhandler, the exception is caught and determined, and then the test application is interrupted, and the piling method is resumed.
Run the test
❯ make test deno test --allow-env --allow-net -l info Check file:///xxx/deno-restful-api-with-postgresql-tdd/.deno.test.ts running 5 tests test should work ... ok (5ms) test UserController #addUser should return added user when add user ... ok (21ms) test UserController #addUser should throw exception about no params given no params when add user ... ok (4ms) test UserController #addUser should throw exception about no correct params given wrong params when add user . ok (3ms) test health check ... ok (4ms) test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (37ms)Copy the code

Service

Service is a Service layer that provides services by combining other services and invoking the underlying data interface layer. For user addition, we only need to pass the user object to the Service that adds the user, and then the Repository handles it. So, our Todo corresponds to:

When the desired user information is passed in, the user information for a specific data structure is returned

Create userService.test. ts and import related dependencies.

// tests/services/UserService.test.ts
import {
  stub,
  Stub,
  assertEquals,
  v4,
} from ".. /.. /deps.ts";
import UserRepo from ".. /.. /src/repositories/userRepo.ts";
import UserService from ".. /.. /src/services/UserService.ts";
import IUser from ".. /.. /src/entity/User.ts";
const { test } = Deno;

test("UserService #addUser should return added user".async() = > {const parameter: IUser = {
    username: "username",
    password: "password"};const registrationDate = (new Date()).toISOString();
  const id = v4.generate();
  constmockedUser: IUser = { ... parameter, id, registrationDate, deleted:false};const userRepo = new UserRepo();
  const createUserStub: Stub<UserRepo> = stub(userRepo, "create");
  createUserStub.returns = [mockedUser];

  const userService = new UserService();
  userService.userRepo = userRepo;

  assertEquals(await userService.addUser(parameter), mockedUser);
  createUserStub.restore();
});
Copy the code

The code logic is simple and requires little explanation. To pass the test, write userService. ts and call the create method of Repository in userService. ts. Therefore, you also need to implement UserRepo simply by adding the create method.

// src/services/UserService.ts
import UserRepo from ".. /repositories/userRepo.ts";
import IUser from ".. /entity/User.ts";

export default class UserService {
  constructor() {
    this.userRepo = new UserRepo();
  }
  userRepo = new UserRepo();
  async addUser(user: IUser) {
    return await this.userRepo.create(user);
  }
Copy the code
Run the test
❯ make test deno test --allow-env --allow-net -l info Check file:///xxx/deno-restful-api-with-postgresql-tdd/.deno.test.ts running 6 tests test should work ... ok (5ms) test UserController #addUser should return added user when add user ... ok (21ms) test UserController #addUser should throw exception about no params given no params when add user ... ok (4ms) test UserController #addUser should throw exception about no correct params given wrong params when add user . ok (3ms) test health check ... ok (4ms) test UserService #addUser should return added user ... ok (1ms) test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (38ms)Copy the code

Repository

Repository typically interacts with a database to persist incoming data into the database. For the user interface, our requirement should be to store the incoming information in the format required by the database and return the result to the Service. Therefore, Todo is roughly as follows:

  • Stores incoming users into the data deficit and returns information about a specific data structure
  • Throw an exception if the basic field is missing in the parameter

The test is as follows:

// tests/repositories/UserRepo.test.ts
import {
  stub,
  Stub,
  Client,
  assertEquals,
  v4,
  assert,
  assertMatch,
  assertThrowsAsync,
} from ".. /.. /deps.ts";
import UserRepo from ".. /.. /src/repositories/userRepo.ts";
import client from ".. /.. /src/Utils/client.ts";
import IUser from ".. /.. /src/entity/User.ts";
import NotFoundException from ".. /.. /src/exception/NotFoundException.ts";
import InvalidedParamsException from ".. /.. /src/exception/InvalidedParamsException.ts";
const { test } = Deno;

test("UserRepo #create should return mocked User given username&password when create".async() = > {const queryStub: Stub<Client> = stub(client, "query");
  const mockedQueryResult = {
    rowCount: 1}; queryStub.returns = [mockedQueryResult];const parameter: IUser = {
    username: "username",
    password: "password"};const userRepo = new UserRepo();
  userRepo.client = client;
  const createdUserResult = awaituserRepo.create(parameter); assertEquals(createdUserResult.username, parameter.username); assertEquals(createdUserResult.password, parameter.password); assert(v4.validate(createdUserResult.id!) ); assertMatch( createdUserResult.registrationDate! .] / [\ d {4} - {2} [\ d] - [\ d] {2} T [\ d] {2}, {2} [d \] : [\ d] {2}. \ [d \] {1, 3} Z /,); queryStub.restore(); }); test("UserRepo #create should throw exception given no value for field when create".async() = > {const parameter: IUser = {
    username: "",
    password: ""};const userRepo = new UserRepo();

  assertThrowsAsync(async() = > {await userRepo.create(parameter)
  }, InvalidedParamsException,
  "should supply valid username and password!")});Copy the code

Because the Repository layer deals with databases, it needs a Repository of processing tools that correspond to database operations. Here we expect to use PostgreSql’s own Client to perform database operations.

In the first test above, we mock the Client’s Query method and return the scheduled data. The create method of UserRepo is then called to determine whether the data field value of the returned data is consistent with the expected value.

Running the test will still fail, so let’s implement the test in the simplest way possible.

Import postgresQL-related dependencies

export { Client } from "https://deno.land/x/postgres/mod.ts";
Copy the code

And define database connection information

// src/config.ts
export const DB_HOST = env.DB_HOST || "localhost";
export const DB_USER = env.DB_USER || "postgres";
export const DB_PASSWORD = env.DB_PASSWORD || "0";
export const DB_DATABASE = env.DB_DATABASE || "postgres";
export const DB_PORT = env.DB_PORT ? parseInt(env.DB_PORT) : 5432;

Copy the code

Get the Client instance that connects to the database

// src/Utils/client.ts
import { Client } from ".. /.. /deps.ts";
import {
  DB_HOST,
  DB_DATABASE,
  DB_PORT,
  DB_USER,
  DB_PASSWORD,
} from ".. /config.ts";

const client = new Client({
  hostname: DB_HOST,
  database: DB_DATABASE,
  user: DB_USER,
  password: DB_PASSWORD,
  port: DB_PORT,
});

export default client;

Copy the code

The database should be connected when the application starts, so the client is introduced at index.ts and the connection is established and managed.

if (import.meta.main) {
+  await client.connect();
+  console.info("Database link successful!");
   const app = await createApplication();
   await listenToServer(app);
+  await client.end();
}

Copy the code
Restart tests
❯ make test deno test --allow-env --allow-net -l info Check file:///xxx/deno-restful-api-with-postgresql-tdd/.deno.test.ts running 8 tests test should work ... ok (2ms) test UserRepo #create should return mocked User given username&password when create ... ok (1ms) test UserRepo #create should throw exception given no value for field when create ... ok (1ms) test UserController #addUser should return added user when add user ... ok (14ms) test UserController #addUser should throw exception about no params given no params when add user ... ok (4ms) test UserController #addUser should throw exception about no correct params given wrong params when add user . ok (2ms) test health check ... ok (3ms) test UserService #addUser should return added user ... ok (1ms) test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (28ms)Copy the code
Request body

The request is validated by RestClient; Now start the application and send the following request;

/ / _resources httpClient/addUser. HTTP POST HTTP / 1.1 the content-type: http://localhost:8000/api/v1/users application/x-www-form-urlencoded name=foo&password=123Copy the code
Request the results
HTTP/1.1 201 Created
content-length: 149
x-response-time: 34ms
content-type: application/json; charset=utf-8

{
  "success": true."data": {
    "username": "foo"."password": "123"."id": "7aea0bb7-e0bc-4f1f-a516-3a43f4e30fb6"."registrationDate": "The 2020-07-27 T14: he. 140 z"}}Copy the code

Exceptions can be created by themselves, which will not be demonstrated here, so as to complete the user-added interface.

packaging

According to the above steps, we can complete the query of a single user (GET:/users/: ID), query all users (GET:/users) and DELETE (DELETE:/users/: ID) interface, fast and efficient. When we finished testing and interfaces, using deno’s command-line tools, we were able to package the entire project as a.js file;

❯ make bundle
mkdir dist
deno bundle src/index.ts dist/platform.js
Bundle file:///xxx/deno-restful-api-with-postgresql-tdd/src/index.ts
Emit "dist/platform.js" (856.11 KB)

Copy the code

For backend applications developed with NodeJs, the dreaded node_modules dependency can be a problem when packaging them. Most NodeJs back-end applications simply update environment variables and deploy them in production. Developers don’t write much of a project file, and the node_modules the application depends on are often tens or even hundreds of times as many as the project file. Then Deno solved the problem very well.

Start the application

If necessary, copy the packaged.js to the target directory. As long as there is a Deno environment, we can start the application directly.

❯ make start APP_PORT=1234 deno run --allow-net --allow-env./dist/platform.js The database link is successful! Application started, and Listen to 127.0.0.1:1234Copy the code

Take the whole mess

Through learning Deno, have some experience;

  • Compatible browserAPI.DenoEngineering can be usedJavascriptandTypescriptProgramming, greatly reducing the cognitive complexity and learning difficulty;
  • If you are usingTypescriptDevelopment, then will avoidDynamic one time cool, reconstruction crematorium“, so it’s recommendedTypescriptTo write applications;
  • Decentralized repositories, distributed as single files, in collaborative development, in order to unify library versions, need to verify dependent versions,DenoProvides the ability to generatelock.jsonTo ensure version dependencies between different collaborators;
  • .

Finally, thanks to Haimen and Yile for proofreading and guidance. With their help, I successfully completed this blog post.

Reference

  • Source: https://github.com/guzhongren/deno-restful-api-with-postgresql-tdd
  • Blog: https://guzhongren.github.io/
  • Figure bed: https://sm.ms/
  • Denoland: https://deno.land/
  • VS Code: https://code.visualstudio.com/
  • Docker: https://www.docker.com/
  • Typescript: https://www.typescriptlang.org/
  • Node: https://nodejs.org/
  • mock: https://github.com/udibo/mock

If you find it useful, you are welcome to contact or tip.

🏆 technology project phase I | talk about Deno some thing…