Dino: Erstellen Sie eine Rest-API mit JWT

Im Vorgriff auf den Start des Kurses "Node.JS Developer" laden wir alle ein, eine offene Lektion zum Thema "Dockerisierung von Node.js-Anwendungen" anzusehen .



Und jetzt teilen wir die traditionelle Übersetzung von nützlichem Material. Viel Spaß beim Lesen.














Seit der ersten Version ist Deno ein Schlagwort für Javascript / TypeScript / Node. Lassen Sie uns in diese Technologie eintauchen, indem wir eine JWT-gesicherte REST-API erstellen.





Es ist ratsam, bereits einige Grundlagen in Node und seinem Ökosystem (Express, Nodemon, Sequelize usw.) zu haben, um diesem Tutorial zu folgen.





Was ist Dino (Deno)?

Deno ist eine einfache, moderne und sichere JavaScript- und TypeScript-Laufzeit, die V8 verwendet und in Rust integriert ist.





Es gibt bereits viele Artikel zu diesem Thema, daher werde ich nicht weiter darauf eingehen. Ich kann dieses empfehlen .





Einführung

Seit der offiziellen Veröffentlichung von V1 ist Deno seit einigen Wochen ein Schlagwort (zum Spaß hier die Beliebtheitskurve für "Deno" -Suchen bei Google).





Typescript and Javascript"?





, JWT REST API .





Node.js Express.





REST API, :









  • ORM





  • CRUD-





  • JWT





REST API JWT, :





  • Deno ( : )





  • VSCode Deno,





( ):





  • Denon





  • Oak





  • Djwt





  • Denodb





  • Bcrypt 





-, , " " .





|-- DenoRestJwt
    |-- controllers/
    |   |-- database/
    |   |-- models/
    |-- helpers/
    |-- middlewares/
    |-- routers/
    |-- app.ts
      
      



Node + Express , Nodemon , Nodemon .





Nodemon - , node.js, Node .





" ", Denon, Deno.





deno install --allow-read --allow-run --allow-write -f --unstable 
https://deno.land/x/denon/denon.ts
      
      



Denon. ( ).





// into denon.json
{
  "$schema": "https://deno.land/x/denon/schema.json",
  "env": {},
  "scripts": {
    "start": {
      "cmd": "deno run app.ts"
    }
  }
}
      
      



! Denon, denon start



:





➜ denon start
[denon] v2.0.2
[denon] watching path(s): *.*
[denon] watching extensions: ts,js,json
[denon] starting `deno run app.ts`
Compile file:///deno-crashtest/app.ts
[denon] clean exit - waiting for changes before restart
      
      



, … ! , app.ts



.





 Oak.





Oak - http- Deno, . Koa, @koa/router



.





Oak :





// app.ts
import { Application, Router, Status } from "https://deno.land/x/oak/mod.ts";

// Initialise app
const app = new Application();

// Initialise router
const router = new Router();

// Create first default route
router.get("/", (ctx) => {
    ctx.response.status = Status.OK;
    ctx.response.body = { message: "It's work !" };
});

app.use(router.routes());
app.use(router.allowedMethods());

console.log("? Deno start !");
await app.listen("0.0.0.0:3001");
      
      



, denon start



.





error: Uncaught PermissionDenied: network access to "0.0.0.0:3001", 
run again with the --allow-net flag
      
      



Deno Node: Deno network



. :





// into denon.json
"scripts": {
    "start": {
      // add --allow-net
      "cmd": "deno run --allow-net app.ts"
    }
  }
      
      



( Postman) localhost:3001 :





{
    "message": "It's work !"
}
      
      



DenoDB ORM ( , Sqlite3). , Sequelize ( ).





Database



Sqlite3.





|-- DenoRestJwt
    |-- controllers/
	|   |-- Database.ts
        |   |-- database/
	|   |   |-- db.sqlite
    |   |-- models/
    |-- app.ts
      
      



// Database.ts
import { Database } from "https://deno.land/x/denodb/mod.ts";

export class DatabaseController {
  client: Database;

	/**
   * Initialise database client
   */
  constructor() {
    this.client = new Database("sqlite3", {
      filepath: Deno.realPathSync("./controllers/database/db.sqlite"),
    });
  }

  /**
   * Initialise models
   */
  async initModels() {
    this.client.link([]);
    await this.client.sync({});
  }
}
      
      



ORM . , realPathSync



, . --allow-read



--allow-write



denon.json



:





"scripts": {
    "start": {
      "cmd": "deno run --allow-write --allow-read --allow-net app.ts"
    }
  }
      
      



, , ORM:





|-- DenoRestJwt
    |-- controllers/
    |   |-- models/
    |       |-- User.ts
    |-- app.ts
      
      



// User.ts
import { Model, DATA_TYPES } from "https://deno.land/x/denodb/mod.ts";
import nanoid from "https://deno.land/x/nanoid/mod.ts";

export interface IUser {
  id?: string;
  firstName: string;
  lastName: string;
  password: string;
}

export class User extends Model {
  static table = "users";
  static timestamps = true;
  
  static fields = {
    id: {
      primaryKey: true,
      type: DATA_TYPES.STRING,
    },
    firstName: {
      type: DATA_TYPES.STRING,
    },
    lastName: {
      type: DATA_TYPES.STRING,
    },
    password: {
      type: DATA_TYPES.TEXT,
    },
  };
  
	// Id will generate a nanoid by default
  static defaults = {
    id: nanoid(),
  };
}
      
      



, . (ps: nanoid



UUID, ).





, , : . Bcrypt:





// inside User's class
import * as bcrypt from "https://deno.land/x/bcrypt/mod.ts";

// ...
static async hashPassword(password: string) {
    const salt = await bcrypt.genSalt(8);
    return bcrypt.hash(password, salt);
}
      
      



, ORM :





// Database.ts
import { User } from "./models/User.ts";

export class DatabaseController {
//...
  initModels() {
      // Add User here
      this.client.link([User]);
      return this.client.sync({});
  }
}
      
      



! , , … 





User controller

, CRUD:





|-- DenoRestJwt
    |-- controllers/
    |   |-- Database.ts
    |   |-- UserController.ts
      
      



import * as bcrypt from "https://deno.land/x/bcrypt/mod.ts";
import { IUser, User } from "./models/index.ts";

export class UserController {
  async create(values: IUser) {
    // Call static user method
    const password = await User.hashPassword(values.password);

    const user: IUser = {
      firstName: values.firstName,
      lastName: values.lastName,
      password,
    };

    await User.create(user as any);

    return values;
  }
  async delete(id: string) {
    await User.deleteById(id);
  }

  getAll() {
    return User.all();
  }

  getOne(id: string) {
    return User.where("id", id).first();
  }

  async update(id: string, values: IUser) {
    await User.where("id", id).update(values as any);
    return this.getOne(id);
  }

  async login(lastName: string, password: string) {
    const user = await User.where("lastName", lastName).first();
    if (!user || !(await bcrypt.compare(password, user.password))) {
      return false;
    }

    // TODO generate JWT

  }
}
      
      



, ORM. JWT.





.





|-- DenoRestJwt
    |-- routers
        |-- UserRoute.ts
      
      



import { Router, Status } from "https://deno.land/x/oak/mod.ts";
import { UserController } from "../controllers/UserController.ts";
import { BadRequest } from "../helpers/BadRequest.ts";
import { NotFound } from "../helpers/NotFound.ts";

// instantiate our controller
const controller = new UserController();

export function UserRoutes(router: Router) {
  return router
    .get("/users", async (ctx) => {
      const users = await controller.getAll();

      if (users) {
        ctx.response.status = Status.OK;
        ctx.response.body = users;

      } else {
        ctx.response.status = Status.NotFound;
        ctx.response.body = [];
      }

      return;
    })
    .post("/login", async (ctx) => {
      if (!ctx.request.hasBody) {
        return BadRequest(ctx);
      }
      const { value } = await ctx.request.body();

      // TODO generate JWT

      ctx.response.status = Status.OK;
      ctx.response.body = { jwt };
    })
    .get("/user/:id", async (ctx) => {
      if (!ctx.params.id) {
        return BadRequest(ctx);
      }

      const user = await controller.getOne(ctx.params.id);
      if (user) {
        ctx.response.status = Status.OK;
        ctx.response.body = user;
        return;
      }

      return NotFound(ctx);
    })
    .post("/user", async (ctx) => {
      if (!ctx.request.hasBody) {
        return BadRequest(ctx);
      }

      const { value } = await ctx.request.body();
      const user = await controller.create(value);

      if (user) {
        ctx.response.status = Status.OK;
        ctx.response.body = user;
        return;
      }

      return NotFound(ctx);
    })
    .patch("/user/:id", async (ctx) => {
      if (!ctx.request.hasBody || !ctx.params.id) {
        return BadRequest(ctx);
      }

      const { value } = await ctx.request.body();
      const user = await controller.update(ctx.params.id, value);

      if (user) {
        ctx.response.status = Status.OK;
        ctx.response.body = user;
        return;
      }

      return NotFound(ctx);
    })
    .delete("/user/:id", async (ctx) => {
      if (!ctx.params.id) {
        return BadRequest(ctx);
      }

      await controller.delete(ctx.params.id);

      ctx.response.status = Status.OK;
      ctx.response.body = { message: "Ok" };
    });
}
      
      



, , .





HTTP . . GitHub! , , :





// app.ts
import { DatabaseController } from "./controllers/Database.ts";
import { UserRoutes } from "./routers/UserRoute.ts";

const userRoutes = UserRoutes(router);
app.use(userRoutes.routes());
app.use(userRoutes.allowedMethods());

await new DatabaseController().initModels();
      
      



JWT

! JWT .





1.

, :





  • , "Authorization".









  •  





  • /





Djwt.





|-- DenoRestJwt
    |-- middlewares/
    |   |-- jwt.ts
      
      



, , .





import { Context, Status } from "https://deno.land/x/oak/mod.ts";
import { validateJwt } from "https://deno.land/x/djwt/validate.ts";

/**
 * Create a default configuration
 */
export const JwtConfig = {
  header: "Authorization",
  schema: "Bearer",
	// use Env variable
  secretKey: Deno.env.get("SECRET") || "",
  expirationTime: 60000,
  type: "JWT",
  alg: "HS256",
};

export async function jwtAuth(
  ctx: Context<Record<string, any>>,
  next: () => Promise<void>
) {
    // Get the token from the request
    const token = ctx.request.headers
      .get(JwtConfig.header)
      ?.replace(`${JwtConfig.schema} `, "");
    
    // reject request if token was not provide
    if (!token) {
      ctx.response.status = Status.Unauthorized;
      ctx.response.body = { message: "Unauthorized" };
      return;
    }
    
    // check the validity of the token
    if (
      !(await validateJwt(token, JwtConfig.secretKey, { isThrowing: false }))
    ) {
      ctx.response.status = Status.Unauthorized;
      ctx.response.body = { message: "Wrong Token" };
      return;
    }
    
    // JWT is correct, so continue and call the private route
    next();
  }
      
      



, , . Deno. Denon: Deno .





{
  "$schema": "<https://deno.land/x/denon/schema.json>",
  // Add env variable
  "env": {
    "SECRET": "ADRIEN_IS_THE_BEST_AUTHOR_ON_MEDIUM"
  },
  "scripts": {
    "start": {
      // add the permission with --allow-env
      "cmd": "deno run --allow-env --allow-read --allow-net app.ts"
    }
  }
}
      
      



(ps: , )





.





|-- DenoRestJwt
    |-- routers
        |-- UserRoute.ts
        |-- PrivateRoute.ts
      
      



:





import { Router, Status } from "https://deno.land/x/oak/mod.ts";
import { jwtAuth } from "../middlewares/jwt.ts";

export function PrivateRoutes(router: Router) {
  // call our middleware before our private route
  return router.get("/private", jwtAuth, async (ctx) => {
    ctx.response.status = Status.OK;
    ctx.response.body = { message: "Conntected !" };
  });
}
      
      



:





import { Router, Status } from "https://deno.land/x/oak/mod.ts";
import { jwtAuth } from "../middlewares/jwt.ts";

export function PrivateRoutes(router: Router) {
  // call our middleware before our private route
  return router.get("/private", jwtAuth, async (ctx) => {
    ctx.response.status = Status.OK;
    ctx.response.body = { message: "Conntected !" };
  });
}
      
      



API /private , :





{
    "message": "Unauthorized"
}
      
      



2.  JWT

. , // TODO generate JWT . User, .





// User.ts
import {
  makeJwt,
  setExpiration,
  Jose,
  Payload,
} from "https://deno.land/x/djwt/create.ts";
import { JwtConfig } from "../../middlewares/jwt.ts";
// ...

export class User extends Model {
// ...
	static generateJwt(id: string) {
	    // Create the payload with the expiration date (token have an expiry date) and the id of current user (you can add that you want)
	    const payload: Payload = {
	      id,
	      exp: setExpiration(new Date().getTime() + JwtConfig.expirationTime),
	    };
	    const header: Jose = {
	      alg: JwtConfig.alg as Jose["alg"],
	      typ: JwtConfig.type,
	    };

	    // return the generated token
	    return makeJwt({ header, payload, key: JwtConfig.secretKey });
	  }
	// ...
}
      
      



:





// UserController.ts

export class UserController {
// ...
	async login(lastName: string, password: string) {
			const user = await User.where("lastName", lastName).first();
			if (!user || !(await bcrypt.compare(password, user.password))) {
				return false;
			}
			// Call our new static method
			return User.generateJwt(user.id);
	 }
}
      
      



, :





// UserRoute.ts

// ...
.post("/login", async (ctx) => {
      if (!ctx.request.hasBody) {
        return BadRequest(ctx);
      }
      const { value } = await ctx.request.body();
			
      // generate jwt
      const jwt = await controller.login(value.lastName, value.password);
      if (!jwt) {
        return BadRequest(ctx);
      }

      ctx.response.status = Status.OK;
      // and return it
      ctx.response.body = { jwt };
    })
// ...
      
      



, , :





// localhost:3001/login
{
    "jwt": 
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IlEyY0ZZcUxKWk5Hc0toN0FWV0hzUiIsImV4cCI6MTU5MDg0NDU2MDM5MH0.drQ3ay5_DYuXEOnH2Z0RKbhq9nZElWCMvmypjI4BjIk"
}
      
      



( )





Authorization



:





// localhost:3001/private with token in headers
{
    "message": "Connected !"
}
      
      



! API ?.





Github: ( , ).





Deno

Depo, :





URL - : npm i



 yarn add



. , Deno, , .





The remote module XXX has not been cached
      
      



  • TypeScript Javascript, . , .





  • : permissions. , , Deno, , permissions . , , . ( )





  • , (https://deno.land/x → 460 NPM → + 1 ).





  • , Deno . + , Node, Deno. , , javascript…






"Node.JS Developer".



" Node.js ".













All Articles